Introduction to Deep Learning

A First Dive Into Deep Learning

  • A deep learning model is a very versatile function, \(f_\theta(x)\), where the input \(x\) is typically high dimensional and the parameter \(\theta\) is high dimensional as well.

  • The function returns an output \(y=f_\theta(x)\), where \(y\) is typically of significantly lower dimension than both \(\theta\) and \(x\).

  • Given large datasets comprised of many \(x\) inputs matching desired outputs \(y\), it is often possible to find good parameter values \(\theta\) such that \(f_\theta(\cdot)\) approximately satisfies the desired relationship between \(x\) and \(y\).

  • The process of finding such \(\theta\) is called training. Once trained, the model \(f_\theta(\cdot)\) can be applied to unseen input data, \(x\), with a hope of making good predictions, classifications, or decisions.

  • Input data \(x\) can be in the form of an image, text, a sound waveform, tabular heterogeneous data, or some other variant.

  • Output data \(y\) can be the probability of a label indicating the meaning/content of the image, a numerical value, or an object of similar form to \(x\) such as translated text in case \(x\) is text, a masked image in case \(x\) is an image, or similar.

ImageNet and VGG19

  • One very popular source of data is the ImageNet database which has been used for the development and benchmarking of many deep learning models.

  • The database has nearly 15 million color images. In many models a subset of about \(1.5\) million images are used for training where the basic form of \(y\) is given by a label which is one of \(1,000\) categories indicating the content of the image.

  • Consider the VGG19 model which is one of several popular (now classical) deep learning models for images. For this model, \(x\) is a \(224\times224\) color image. It is thus comprised of \(3\times224\times224 = 150,528\) values, with every coordinate of \(x\) representing the intensity of a specific pixel color (red, green, or blue).

  • The output \(y\) is a \(1,000\) dimensional vector where each coordinate of the vector corresponds to a different type of object, e.g. car, banana, etc. The numerical value of the coordinate \(y_i\), where say \(i\) is the index which matches banana is the model’s prediction of the probability that the input \(x\) is a picture of a banana.

  • In 2014 when VGG19 was introduced, it was fed about \(1.5\) million ImageNet images, \(x\), each with a corresponding label, e.g. banana, which is essentially a desired output \(y\). The process of training VGG19 then involved finding good parameters \(\theta\) so that when the model is presented with a new unseen image \(x\), it predicts the label of the image well.

  • Note that in VGG19, \(\theta\) has a huge number of parameters; \(144\) million!

  • \(1.5\times 10^6\) input data samples each of size of about \(1.5\times10^5\) (pixel values).

  • Training data size has about \(2.25 \times 10^{11}\) values (numbers). This data was then used to learn about \(1.44\times10^8\) parameters.

  • At the time when this specific model was introduced it took days to train and much longer to fine tune. Today such a model may take around 8 hours to train.

  • Further, it can take about a fifths of a second to make a prediction with this model, that is to evaluate \(f_\theta(x)\).

  • This training of VGG19 from scratch on ImageNet is not something one would typically do in practice.

  • We use the pre-trained VGG19 model parameters and adapt them based on another dataset called Fruits 360 hich has nearly \(100,000\) images of fruits

  • Here we use the fast.ai library with the Python language which also uses PyTorch under the hood.

  • However, we could have presented alternatives with other languages (e.g. Julia or R) as well as other deep learning libraries such as for example Keras which uses Tensorflow.

  • code here https://colab.research.google.com/drive/1YOjnlAqY71PspLn0QzoYl5SmcEmXr4GP?usp=sharing

Beyond Classification

  • Semantic segmentation of images

  • Object detection and localization

A Taste of Tasks and Architectures

  • Convolutional Neural Networks

  • Generative Adversarial Networks (GAN)

  • Encoder, Decoder, and Auto-encoder Models

  • Recurrent Neural Networks, LSTM, and Other Sequence Models

  • Deep Reinforcement Learning

  • Fully Connected Neural Networks

  • Transformer architectures

  • Diffusion models

Neural Networks as Artificial Brains?

  • Brain: estimated 85 billion neurons

  • A single human action: movement of an arm may induce the firing of around 50 million such neurons, whereas the identification of a visual object may use the bulk of 150 million neurons that are in the visual cortex.

  • Deep neural network models are neither brains nor attempts to create artificial brains.

  • Nevertheless, the development of these models is highly motivated by the biological structure of the brain.

  • The basic building block of a deep neural network model is the (artificial) neuron abstracting the synapse connection between neurons via a single number called an activation value.

  • Pioneering and landmark work in AI research was inspired by neuroscience. Since brains are essentially the only complete proof we have for the existence of what we call ‘’general intelligence’’

  • Many tasks of deep learning models involve the mimicking of human level (or animal level) tasks such understanding images or conversational tasks.

  • The most well known benchmarks in the world of artificial intelligence is the Turing test, originally named the imitation game when introduced by Alan Turing in 1950.

  • It is essentially a test to see if a computer can engage in long conversation with a human, without another observing human distinguishing between the computer and the human.

  • Deep learning models generally only achieve narrow tasks such as pattern recognition, or playing specific games, as opposed to artificial general intelligence (AGI) tasks of creative problem solving.

Data: Seen, Unseen, Training, Testing

  • Distinguish between seen data and unseen data.

  • Seen data is available for learning, namely for training of models, model selection, parameter tuning, and testing of models.

  • Unseen data is essentially unlimited. All data from the real world that is not available while learning takes place but is later available when the model is used. This can be data from the future, or data that was not collected or labelled with the seen data.

Feed-forward deep neural network

  • Let start with a simplest neural network

A Shallow Neural Network: sigmoid model

  • A map from \(x\in \Re^p\) to \(y\in\{0,1\}\).

  • Input to output
  • Input \(x\) is the features and \(y\) is the response

Sigmoid model is a logistic model

It gives a probability as output:

\[\begin{equation} \label{eq:first-shallow-view} \hat{y}=\underbrace{\sigma\left(\overbrace{b+w^\top x}^{z}\right)}_{a}. \end{equation}\]

Example of data for such model:

Consider the data from Breast Cancer Wisconsin (Diagnostic) (WBCD) dataset

  • Goal is to predict if a benign (Y = 0) or a malignant (Y = 1) lumps of a breast mass.

  • Historical data consists of a large number of patient records

  • 30 (=\(d\)) characteristics of individual cells of breast cancer

  • Use a machine learning algorithm that observes these data, produces a predictor

  • Predictor takes as input 30 values, returns a single Boolean prediction (0 or 1)

  • This is a classifier, since we are predicting an outcome that takes only two values

  • Evaluate model by its error rate on a separate test set of data, not used to develop the model

  • A probabilistic model returns a probability that the patient has a malignant lump, not just a Boolean

Multi-classification task: Softmax model

  • Softmax:

\[ \hat{y}=\underbrace{S_{\text{softmax}} \big(\overbrace{b+W x}^{z\in {\mathbb R}^K}\big)}_{a\in {\mathbb R}^K}, \qquad \text{with} \qquad S_{\textrm{Softmax}}(z) = \frac{1}{\sum_{i=1}^{K} e^{z_i}} \begin{bmatrix} e^{z_1} \\ \vdots\\ e^{z_{K}}\\ \end{bmatrix}. \]

The General Fully Connected Architecture

A Model Based on Function Composition

The goal of a feedforward network is to approximate some function \(f^*: \mathbb{R}^p \longrightarrow \mathbb{R}^q\). A feedforward network model defines a mapping \(f_\theta: \mathbb{R}^p \longrightarrow \mathbb{R}^q\) and learns the value of the parameters \(\theta\) that ideally result in

\[ f^*(x) \approx f_\theta(x) \]

The function \(f_\theta\) is recursively composed via a chain of functions:

\[f_\theta(x)=f_{\theta[L]}^{[L]}\left(f_{\theta[L-1]}^{[L-1]}\left(\ldots\left(f_{\theta[1]}^{[1]}(x)\right) \ldots\right)\right)\]

  • \(f_{{\mathbb{\theta}}^{[\ell]}}^{(\ell)}: {\mathbb R}^{N_{\ell-1}} \longrightarrow {\mathbb R}^{N_\ell}\) for the \(\ell\)th layer associated parameters \({\mathbb{\theta}}^{[\ell]} \in \Theta^{[\ell]}\), where \(\Theta^{[\ell]}\) is the parameter space for the \(\ell\)th layer.

  • The of the network is \(L\).

  • \(N_0 = p\) (the number of features) and \(N_L = q\) (the number of output variables).

  • The number of neurons in the network is \(\sum_{\ell=1}^L N_\ell\).

Affine Transformations Followed by Activations

  • The function \(f_{{\mathbb{\theta}}^{[\ell]}}^{(\ell)}\) is defined by an affine transformation followed by an activation function .

  • Activation functions are the means of introducing non-linearity into the model.

  • The output of layer \(\ell\) is represented by the vector \(a^{[\ell]}\)

  • The intermediate result of the affine transformation is represented by the vector \(z^{[\ell]}\)

  • We denote the output of the model via \(\hat{y}\) and hence, \[ \hat{y} = a^{[L]} = f_\theta(x). \]

The action of \(f_{{\mathbb{\theta}}^{[\ell]}}^{(\ell)}\) is represented as

  • \(a^{[0]} = x\). The parameters of the \(\ell\)th layer, \(\mathbb{\theta}^{[\ell]}\), are given by:

  • \(N_{\ell} \times N_{\ell-1}\) weight matrix \(W^{[\ell]} = \big(w^{[\ell]}_{i,j}\big)\)

  • \(N_\ell\) dimensional bias vector \(b^{[\ell]} = (b_i^{[\ell]})\).

  • Thus the parameter space of the layer is \(\Theta^{[\ell]} = \Re^{N_{\ell} \times N_{\ell-1}} \times \Re^{N_{\ell}}\).

  • The activation function \(S^{[\ell]}: \Re^{N_{\ell}} \longrightarrow \Re^{N_{\ell}}\) is a nonlinear multivalued function . For \(\ell = 1,\ldots,L-1\) it is generally of the form \[\begin{equation} \label{eqn:activation-function-vector} S^{[\ell]}(z)=\left[\sigma^{[\ell]}\left(z_{1}\right) ~ \ldots ~\sigma^{[\ell]}\left(z_{N_{\ell}}\right)\right]^{\top},% \ \ \ \ell=1,\ldots,L-1, \end{equation}\]

where \(\sigma^{[\ell]}: \Re \to \Re\) is typically an activation function common to all hidden layers. For the output layer, \(\ell = L\), it is often of a different form depending on the task at hand.

Output layer, \(\ell = L\)

  • For multi-class classification: a softmax function is used, for binary classification, a sigmoid
  • The output of the network is a vector of probability values determining class membership.
  • In order to get a class label prediction one can convert the predicted probability scores into a class label using a chosen threshold, or more simply maximum a posteriori probability

One Layer NN

\[\left\{ \begin{eqnarray*} \color{Green} {z_1^{[1]} } &=& \color{Orange} {w_1^{[1]}} ^T \color{Red}x + \color{Blue} {b_1^{[1]} } \hspace{2cm}\color{Purple} {a_1^{[1]}} = \sigma( \color{Green} {z_1^{[1]}} )\\ \color{Green} {z_2^{[1]} } &=& \color{Orange} {w_2^{[1]}} ^T \color{Red}x + \color{Blue} {b_2^{[1]} } \hspace{2cm} \color{Purple} {a_2^{[1]}} = \sigma( \color{Green} {z_2^{[1]}} )\\ \color{Green} {z_3^{[1]} } &=& \color{Orange} {w_3^{[1]}} ^T \color{Red}x + \color{Blue} {b_3^{[1]} } \hspace{2cm} \color{Purple} {a_3^{[1]}} = \sigma( \color{Green} {z_3^{[1]}} )\\ \color{Green} {z_4^{[1]} } &=& \color{Orange} {w_4^{[1]}} ^T \color{Red}x + \color{Blue} {b_4^{[1]} } \hspace{2cm} \color{Purple} {a_4^{[1]}} = \sigma( \color{Green} {z_4^{[1]}} ) \end{eqnarray*}\right.\]

where \(x=(x_1,x_2,x_3)^T\) and \(w_j^{[1]}=(w_{j,1}^{[1]},w_{j,2}^{[1]},w_{j,3}^{[1]},w_{j,4}^{[1]})^T\) (for \(j=1,\ldots,4\)).

Then, the output layer is defined by:

\[\begin{eqnarray*} \color{Green} {z_1^{[2]} } &=& \color{Orange} {w_1^{[2]}} ^T \color{purple}a^{[1]} + \color{Blue} {b_1^{[2]} } \hspace{2cm}\color{Purple} {a_1^{[2]}} = \sigma( \color{Green} {z_1^{[2]}} )\\ \end{eqnarray*}\]

where \(a^{[1]}=(a^{[1]}_1,\ldots,a^{[1]}_4)^\top\) and \(w_1^{[2]}=(w_{1,1}^{[2]},w_{1,2}^{[2]},w_{1,3}^{[2]},w_{1,4}^{[2]})^\top\)

One can use matrix representation for efficiency computation:

\[\begin{equation} \begin{bmatrix} \color{Orange}- & \color{Orange} {w_1^{[1]} }^T & \color{Orange}-\\ \color{Orange}- & \color{Orange} {w_2^{[1] } } ^T & \color{Orange}- \\ \color{Orange}- & \color{Orange} {w_3^{[1]} }^T & \color{Orange}- \\ \color{Orange}- & \color{Orange} {w_4^{[1]} }^T & \color{Orange}- \end{bmatrix} \begin{bmatrix} \color{Red}{x_1} \\ \color{Red}{x_2} \\ \color{Red}{x_3} \end{bmatrix} + \begin{bmatrix} \color{Blue} {b_1^{[1]} } \\ \color{Blue} {b_2^{[1]} } \\ \color{Blue} {b_3^{[1]} } \\ \color{Blue} {b_4^{[1]} } \end{bmatrix} = \begin{bmatrix} \color{Orange} {w_1^{[1]} }^T \color{Red}x + \color{Blue} {b_1^{[1]} } \\ \color{Orange} {w_2^{[1] } } ^T \color{Red}x +\color{Blue} {b_2^{[1]} } \\ \color{Orange} {w_3^{[1]} }^T \color{Red}x +\color{Blue} {b_3^{[1]} } \\ \color{Orange} {w_4^{[1]} }^T \color{Red}x + \color{Blue} {b_4^{[1]} } \end{bmatrix} = \begin{bmatrix} \color{Green} {z_1^{[1]} } \\ \color{Green} {z_2^{[1]} } \\ \color{Green} {z_3^{[1]} } \\ \color{Green} {z_4^{[1]} } \end{bmatrix} \end{equation}\]

and by defining
\[\color{Orange}{W^{[1]}} = \begin{bmatrix} \color{Orange}- & \color{Orange} {w_1^{[1]} }^T & \color{Orange}-\\ \color{Orange}- & \color{Orange} {w_2^{[1] } } ^T & \color{Orange}- \\ \color{Orange}- & \color{Orange} {w_3^{[1]} }^T & \color{Orange}- \\ \color{Orange}- & \color{Orange} {w_4^{[1]} }^T & \color{Orange}- \end{bmatrix} \hspace{2cm} \color{Blue} {b^{[1]}} = \begin{bmatrix} \color{Blue} {b_1^{[1]} } \\ \color{Blue} {b_2^{[1]} } \\ \color{Blue} {b_3^{[1]} } \\ \color{Blue} {b_4^{[1]} } \end{bmatrix} \hspace{2cm} \color{Green} {z^{[1]} } = \begin{bmatrix} \color{Green} {z_1^{[1]} } \\ \color{Green} {z_2^{[1]} } \\ \color{Green} {z_3^{[1]} } \\ \color{Green} {z_4^{[1]} } \end{bmatrix} \hspace{2cm} \color{Purple} {a^{[1]} } = \begin{bmatrix} \color{Purple} {a_1^{[1]} } \\ \color{Purple} {a_2^{[1]} } \\ \color{Purple} {a_3^{[1]} } \\ \color{Purple} {a_4^{[1]} } \end{bmatrix}\] we can write \[\color{Green}{z^{[1]} } = W^{[1]} x + b ^{[1]}\] and then by applying Element-wise Independent activation function \(\sigma(\cdot)\) to the vector \(z^{[1]}\) (meaning that \(\sigma(\cdot)\) are applied independently to each element of the input vector \(z^{[1]}\)) we get:

\[\color{Purple}{a^{[1]}} = \sigma (\color{Green}{ z^{[1]} }).\] The output layer can be computed in the similar way:

\[\color{YellowGreen}{z^{[2]} } = W^{[2]} a^{[1]} + b ^{[2]}\]

where

\[\color{Orange}{W^{[2]}} = \begin{bmatrix} \color{Orange} {w_{1,1}^{[2]} } \\ \color{Orange} {w_{1,2}^{[2]} } \\ \color{Orange} {w_{1,3}^{[2]} } \\ \color{Orange} {w_{1,4}^{[2]} } \\ \end{bmatrix} \hspace{2cm} \color{Blue} {b^{[2]}} = \begin{bmatrix} \color{Blue} {b_1^{[2]} } \\ \color{Blue} {b_2^{[2]} } \\ \color{Blue} {b_3^{[2]} } \\ \color{Blue} {b_4^{[2]} } \end{bmatrix} \]

and finally:

\[\color{Pink}{a^{[2]}} = \sigma ( \color{LimeGreen}{z^{[2]} })\longrightarrow \color{red}{\hat{y}}\]

A short practice

The Forward Pass

We can generalize this simple previous neural network to a Multi-layer fully-connected neural networks by sacking more layers get a deeper fully-connected neural network defining by the Forward Pass equations.

\[\begin{equation} \label{eqn:forward-pass} \begin{array}{l} \text{Layer 1}~~ \left\{ \begin{array}{rrcl} \quad~~&{z^{[1]} } &=&W^{[1]}\overbrace{x}^{\text{Input}} +b^{[1]} \\ \quad~~&{a^{[1]} } &=&S^{[1]}(z^{[1]}) \\ \end{array} \right.\\[20pt] \text{Layer 2}~~ \left\{ \begin{array}{rrcl} \quad~~&{z^{[2]} } &=&W^{[2]}a^{[1]} +b^{[2]} \\ \quad~~&{a^{[2]} } &=&S^{[2]}(z^{[2]}) \\ \end{array} \right.\\[20pt] \quad \vdots\\[20pt] \text{Layer }L~~ \left\{ \begin{array}{rrcl} &{z^{[L]} } &=&W^{[L]}a^{[L-1]} +b^{[L]} \\ \underbrace{\hat{y}}_{\text{output}} ~= &{a^{[L]} } &=&S^{[L]}(z^{[L]}). \\ \end{array} \right. \end{array} \end{equation}\]

  • The computational cost of such a forward pass is at an order of the total number of weights.
  • At the \(\ell\)-th layer, the cost to compute \({z^{[\ell]} } = W^{[\ell]}a^{[\ell-1]} + b^{[\ell]}\) and \({a^{[\ell]} } =S^{[\ell]}(z^{[\ell]})\) are of the order \(N_\ell \times N_{\ell-1} + N_\ell\) and \(N_\ell\), respectively.
  • Total computational cost is of the order \(\sum_{\ell=1}^L N_\ell (N_{\ell-1} +2) \approx \sum_{\ell=1}^L N_\ell N_{\ell-1}\), which is the total number of weights.

An Example with Concrete Dimensions

  • Consider the network with one-hidden layer.
  • \(N_0=p = 4\), \(N_1=5\), and \(N_2=q=1\).
  • The dimension of \(W^{[1]}\) is \(5 \times 4\)
  • The dimension of \(b^{[1]}\) is \(5\), the dimension of \(W^{[2]}\) is \(1 \times 5\), and the dimension of \(b^{[2]}\) is \(1\).

\[\begin{equation} \label{eqn:opened-out-example-1} f_\theta(x)=\underbrace{S^{[2]}(\overbrace{W^{[2]}\underbrace{S^{[1]}(\overbrace{W^{[1]}x+b^{[1] }}^{z^{[1]}} )}_{a^{[1]}}+b^{[2]}}^{z^{[2]}})}_{a^{[2]}}, \end{equation}\]

  • number of parameters in \(\theta\) is \(5 \times 4 + 5 + 1\times 5 + 1 = 31\).

The deeper network below:

is represented by

\[\begin{equation} \label{eqn:opened-out-example-2} f_\theta(x)=S^{[4]}(W^{[4]}S^{[3]}(W^{[3]}S^{[2]}(W^{[2]}S^{[1]}(W^{[1]}x+b^{[1]})+b^{[2]}) +b^{[3]})+b^{[4]}) \end{equation}\]

The number of parameters is

\[ \underbrace{4\times4 + 4}_{\text{Hidden layer 1}} + \underbrace{3\times 4 + 3}_{\text{Hidden layer 2}}+ \underbrace{5\times 3 + 5}_{\text{Hidden layer 3}} + \underbrace{1 \times 5 +1}_{\text{Output layer}} = 61. \]

The Scalar Based View of the Model

The \(i\)th neuron of layer \(\ell\), with \(i=1,\ldots,N_\ell\), is typically composed of both \(z^{[\ell]}_i\) and \(a^{[\ell]}_i\). The transition from layer \(\ell-1\) to layer \(\ell\) takes the output of layer \(\ell-1\), an \(N_{\ell-1}\) dimensional vector, and operates on it as follows,

\[\begin{equation} \label{eq:scalar-nn-full} \substack{\text{Affine} \\ \text{Transformation}}:\left\{\begin{array}{lll} z_{1}^{[\ell]}&=&w_{(1)}^{[\ell]^{\top}} a^{[\ell-1]} +b_{1}^{[\ell]} \\ z_{2}^{[\ell]}&=&w_{(2)}^{[\ell]^{\top}} a^{[\ell-1]} +b_{2}^{[\ell]} \\ &\vdots&\\ z_{N_\ell}^{[\ell]}&=&w_{(N_\ell)}^{[\ell]^{\top}} a^{[\ell-1]} +b_{N_\ell}^{[\ell]} \end{array}\right. \hspace{0.1cm} \Rightarrow \hspace{0.1cm}\substack{\text{Activation } \\ \text{Step}}:\left\{\begin{array}{lll} a_{1}^{[\ell]} &=&\sigma\left(z_{1}^{[\ell]}\right) \\ a_{2}^{[\ell]} &=&\sigma\left(z_{2}^{[\ell]}\right) \\ &\vdots&\\ a_{N_\ell}^{[\ell]} &=&\sigma\left(z_{N_\ell}^{[\ell]}\right) \end{array}\right., \end{equation}\] where, \[ {w_{(j)}^{[\ell]}}^\top = \left[w_{j, 1}^{[\ell]}~ \ldots ~ w_{j, N_{\ell-1}}^{[\ell]}\right], \qquad \text{for} \qquad j=1,\ldots,N_\ell, \] is the \(j\)th row of the weight matrix \(W^{[\ell]}\), and \(b^{[\ell]}_j\) is the \(j\)th element of the bias vector \(b^{[\ell]}\). Hence the parameters associated with neuron \(j\) in layer \(\ell\), are \({w_{(j)}^{[\ell]}}^\top\) and \(b^{[\ell]}_j\).

How do we count layers in a neural network?

When counting layers in a neural network we count hidden layers as well as the output layer, but we don’t count an input layer.

It is a four layer neural network with three hidden layers.

Activation Function Alternatives

Let first create a simple plot function for each activation function and its derivative.

Scalar Activations and their Derivatives

library(ggplot2)
f <- function(x) {x}
plot_activation_function <- function(f, title, range){
  ggplot(data.frame(x=range), mapping=aes(x=x)) + 
    geom_hline(yintercept=0, color='red', alpha=1/4) +
    geom_vline(xintercept=0, color='red', alpha=1/4) +
    stat_function(fun=f, colour = "dodgerblue3") +
    ggtitle(title) +
    scale_x_continuous(name='x') +
    scale_y_continuous(name='') +
    theme(plot.title = element_text(hjust = 0.5))
}

Sigmoid function

\[\sigma(z)=g (z)=\frac{1}{1+e^{-z}}\]

Its derivative :

\[\frac{d}{dz}\sigma(z)=\sigma(z)(1-\sigma(z))\]

f <- function(x){1 / (1 + exp(-x))}
df <- function(x){f(x)*(1-f(x))}
plotf <- plot_activation_function(f, 'Sigmoid', c(-4,4))
plotdf <- plot_activation_function(df, 'Derivative', c(-4,4))
library(ggpubr)
ggarrange(plotf,plotdf,nrow=1,ncol=2)

ReLU function

A recent invention which stands for Rectified Linear Units.

\[ReLU(z)=\max{(0,𝑧)}\]

Despite its name and appearance, it’s not linear and provides the same benefits as Sigmoid but with better performance.

Its derivative :

\[\frac{d}{dz}ReLU(z)= \Bigg\{ \begin{matrix} 1 \enspace if \enspace z > 0 \\ 0 \enspace if \enspace z<0 \\ undefined \enspace if \enspace z = 0 \end{matrix}\]

rec_lu_func <- function(x){ ifelse(x < 0 , 0, x )}
drec_lu_func <- function(x){ ifelse(x < 0 , 0, 1)}
plotf <- plot_activation_function(rec_lu_func, 'ReLU', c(-4,4))
plotdf <- plot_activation_function(drec_lu_func, 'Derivative', c(-4,4))
ggarrange(plotf,plotdf,nrow=1,ncol=2)

LeakyRelu function

Leaky Relu is a variant of ReLU. Instead of being 0 when \(z<0\), a leaky ReLU allows a small, non-zero, constant gradient \(\alpha\) (usually, \(\alpha=0.01\)). However, the consistency of the benefit across tasks is presently unclear.

\[LeaklyReLU(z)=\max{(\alpha z,𝑧)}\]

Its derivative :

\[\frac{d}{dz}LeaklyReLU(z)= \begin{cases}\alpha & if \ \ z< 0 \\1 & if \ \ z\geq0\\\end{cases}\]

rec_lu_func <- function(x){ ifelse(x < 0 , 0.01*x, x )}
drec_lu_func <- function(x){ ifelse(x < 0 , 0.01, 1)}
plotf <- plot_activation_function(rec_lu_func, 'LeaklyReLU', c(-4,4))
plotdf <- plot_activation_function(drec_lu_func, 'Derivative', c(-4,4))
ggarrange(plotf,plotdf,nrow=1,ncol=2)

Tanh function

Tanh squashes a real-valued number to the range \([-1, 1]\). It’s non-linear. But unlike Sigmoid, its output is zero-centered. Therefore, in practice the tanh non-linearity is always preferred to the sigmoid nonlinearity.

\[tanh(z) =\frac{e^{z}-e^{-z}}{e^{z}+e^{-z}}\]

Its derivative :

\[\frac{d}{dz}tanh(z)=1-tanh(z)^2\]

tanh_func <- function(x){tanh(x)}
dtanh_func <- function(x){1-(tanh(x))**2}
plotf <- plot_activation_function(tanh_func, 'TanH', c(-4,4))
plotdf <- plot_activation_function(dtanh_func, 'Derivative', c(-4,4))
ggarrange(plotf,plotdf,nrow=1,ncol=2)

A recap

Non Scalar Activations and their Derivatives

  • Some layers also use non-scalar activation functions: \(S^{[\ell]}: \Re^{N_\ell} \to \Re^{N_\ell}\) a vector to vector function.

-The most common example of this is the softmax activation function, typically used for classification in the last layer \(\ell = L\).

  • We now denote \(N_L = K\) since to deal with classification of \(K\) classes, \[\begin{equation} \label{eqn:last-is-softmax} a^{[L]} = S_{\text{softmax}}(z^{[L]}). \end{equation}\]

The \(\Re^{K} \to \Re^{K}\) softmax activation function is defined as, \[ S_{\text{softmax}}(z) = \frac{1}{\sum_{i=1}^{K} e^{z_i}} \begin{bmatrix} e^{z_1} & \cdots & e^{z_{K}} \end{bmatrix}^\top, \]

The Expressive Power of Neural Networks

  • Neural networks are known for being able to \(\color{Blue}{\textrm{approximate}}\) arbitrarily complex functions.

  • \(\color{Red}{\textrm{A General Approximation Result}}\):

Theorem. Consider a continuous function \(f^*: {\cal K} \to \Re^q\) where \({\cal K} \subset \Re^p\) is a compact set. Then for any any \(\sigma^{[1]}(\cdot)\) non-polynomial activation function and any \(\varepsilon > 0\) there exists an \(N_1\) and parameters \(W^{[1]}\in\Re^{N_1\times p}\), \(b^{[1]} \in \Re^{N_1}\), and \(W^{[2]}\in \Re^{q\times N_1}\), such that the function \[ {f}_\theta(x)=W^{[2]}S^{[1]}(W^{[1]}x+b^{[1]}), \] satisfies \(||{f}_\theta(x)-f^*(x)||<\varepsilon\) for all \(x\in {\cal K}\).

  • Hence this theorem states that essentially all functions can be approximated to arbitrary precision dictated via \(\varepsilon\).

  • Practically for complex functions \(f^*(\cdot)\) and small \(\varepsilon\) one may need large \(N_1\).

  • Yet, the theorem states that it is always possible.

  • Let try it in a short practice using R.

  • Let try it in a short practice using Python (google collab).

The Strength of a Hidden Layer for Classification

  • Shallow neural networks such as logistic regression or softmax regression can be used to create classifiers with linear decision boundaries.

  • For cases where more general decision boundaries are needed, one may attempt to create additional transformed features while still using these basic models.

  • Expressiveness of models with a single hidden layer (or more) can yield a versatile alternative to shallow networks.

Time to a short practice

Expressivity example

Stylized Functions via Simple Models

  • Expressive power of feedforward neural networks by considering specific stylized functions

  • multiplication of two inputs: A simple construction of a single hidden layer network with \(p=2\) and \(q=1\), allows to create a function \(f_\theta(\cdot)\), parametrized by \(\lambda >0\), such that for input \(x=(x_1,x_2)\), the function approximately implements multiplication of inputs, \[\begin{equation} f_\theta(x_1,x_2) \approx x_1 x_2. \end{equation}\]

  • Importantly, the approximation error vanishes as \(\lambda \to 0\)

  • Requires only \(N_1 = 4\) neurons in the single hidden layer.

  • The activation function of the output layer is the identity.

  • There are no bias terms, and the weight matrices are,

\[ W^{[1]} = \begin{bmatrix} \lambda & \lambda \\ -\lambda & -\lambda \\ \lambda & -\lambda \\ -\lambda & \lambda \\ \end{bmatrix}, \qquad \text{and} \qquad W^{[2]}= \begin{bmatrix} \mu & \mu & -\mu & -\mu\\ \end{bmatrix}, \]

with \(\mu=\big({4\lambda^2\ddot{\sigma}(0)}\big)^{-1}\).

  • \(\ddot{\sigma}(0)\) represents the second derivative of the scalar activation function of the hidden layer (\(\ell = 1\)) at \(0\). Hence the model assumes \(\sigma^{[1]}(\cdot)\) is twice differentiable (at \(0\)) with a non-zero second derivative at zero.

  • It turns out that

\[ f_\theta(x_1, x_2) = \frac{\sigma\big(\lambda(x_1+x_2)\big)+\sigma\big(\lambda(-x_1-x_2)\big)-\sigma\big(\lambda(x_1-x_2)\big)-\sigma\big(\lambda(-x_1+x_2)\big)}{4 \lambda^2\ddot{\sigma}(0)}, \]

We may now use a Taylor expansion of \(\sigma(\cdot)\) around the origin,

\[\begin{equation} \label{eq:taylor-sigma} \sigma(u)=\sigma(0)+\dot{\sigma}(0) u + \ddot{\sigma}(0) \frac{u^{2}}{2}+{O}\left(u^{3}\right), \end{equation}\]

with \(O(h^k)\) denoting a function such that \(O(h^k)/h^k\) goes to a constant as \(h \to 0\).

  • Thus the approximation multiplication of two input features \(x_1\) and \(x_2\) (noted \(f_\theta(x_1, x_2)\)) is given by

\[ f_\theta(x_1, x_2) \equiv \frac{\sigma\big(\lambda(x_1+x_2)\big)+\sigma\big(\lambda(-x_1-x_2)\big)-\sigma\big(\lambda(x_1-x_2)\big)-\sigma\big(\lambda(-x_1+x_2)\big)}{4 \lambda^2\ddot{\sigma}(0)}=x_1 x_2\left(1+{O}\left(\lambda ( x_1^{2}+x_2^{2})\right)\right). \]

Hence as \(\lambda \to 0\) the desired goal becomes exact.

Feature Focus with Neural Networks

Feature engineering: additional features by transforming existing features.

Example: consider \({p}\) features \({x}_1,\ldots,{x}_{{p}}\).

  • we wish to construct \(\tilde{p} = p(p+1)/2\) features based on all possible pairwise interactions \({x}_i {x}_j\) for \(i,j =1,\ldots,{p}\).

  • For instance if \({p} = 1,000\) then we arrive at \(\tilde{p} \approx 500,000\).

  • Linear model acting on the transformed features \(\tilde{x}\) or a single hidden layer neural network model acting on the original features \(x\) ?.

  • Linear model we have \(\tilde{f}_\theta(\tilde{x}) = \tilde{w}^\top \tilde{x}\) where the learned weight vector \(\tilde{w}\), has \(\tilde{p}\) parameters.

  • Single hidden layer NN, there are \(p\) inputs, \(q=1\) output, and \(N_1\) units in the hidden layer. Thus the number of parameters: \(N_1 \times {p} + N_1 + N_1 + 1\)

  • Not all interactions (product features) are relevant.

  • Let consider only a fraction \(\alpha\) of the interactions are relevant.

  • The multiplication example requires 4 hidden units to approximate an interaction.

  • This example hints at the fact that with \(N_1 \approx 4 \, \alpha \, p\) hidden units we may be able to capture the key interactions.

  • The basic linear model still requires the full set of parameters. With this, compare the number of parameters,

\[ \underbrace{\frac{1}{2}p(p+1)}_{\text{Linear Model}} \qquad \text{vs.} \qquad \underbrace{4 \alpha p(p+2)+1}_{\text{Neural Network}}. \]

  • Observe that \(p^2\) is the dominant term in both models but for \(\alpha < 1/8\) and large \(p\), the neural network model has less parameters.

  • With \(p=1,000\) if \(\alpha = 0.02\) (\(20\) significant interactions) then the linear model has an order of \(500,000\) parameters while the neural network only has order of \(80,000\) parameters.

Higher Model Complexity by an Increase of Depth

  • To gain high expressive power, this model might require a very large number of units (\(N_1\) needs to be very large). Hence gaining significant expressive power may require a very large number of parameters.

  • The power of deep learning then arises via repeated composition of non-linear activations functions via an increase of depth (an increase of \(L\)).

  • Note first that if the identity activation function is used in each hidden layer, then the network reduces to a shallow neural network, \[ f_\theta(x)=S^{[L]}(\tilde{W} x + \tilde{b}), \]

\[ \tilde{W} = W^{[L]}W^{[L-1]}\cdot \ldots \cdot W^{[1]}, \qquad \text{and} \qquad \tilde{b} = \sum_{\ell = 1}^L \Big(\prod_{\tilde{\ell} = \ell + 1}^L W^{[\tilde{\ell}]}\Big) b^{[\ell]}. \]

  • The model reduces to be a linear (affine) model. Thus, we have no gain by going deeper and adding multiple layers with identity activations.

  • The expressivity of the neural network comes from the composition of non-linear activation functions.

  • The repeated compositions of such functions can reduce the number of units needed in each layer in comparison to a network with a single hidden layer. A consequence is that the parameter space is reduced as well.

Example

  • We revisit the previous example involving models using interactions of order 2 (i.e. \(x_ix_j\)). Let us consider a higher complexity model by exploiting potential high-order interactions, namely products of r inputs.

  • Consider a fully connected feedforward network with \(p\) inputs, \(q=1\) output, and \(L \ge 2\) layers with same size (\(N^{[\ell]}=N\) in each layer).

  • With \(N \approx 4 \, \alpha \, p\) hidden units we may be able to capture the \(\alpha p\) relevant interactions of order \(r=2\).

  • Then by moving forward in the network, the subsequent addition of a layer with \(N \approx 4 \, \alpha \, p\) hidden units will capture interactions of order \(r=2^2\) and so on, until we capture interaction of order \(r=2^{L}\) at the output layer.

  • Hence to achieve interactions of order \(r\) we may require \(L \approx \log_2 r\) or \(L = \lceil \log_2 r \rceil\).

The number of parameters is,

\[ \underbrace{N \times {p} +N}_{\text{First hidden layer}}+ \underbrace{(L-2)\times(N^2 + N)}_{\text{Inner hidden layers}} + \underbrace{N+1}_{\text{Output layer}} \approx L N^2. \]

  • For example, assume we wish to have a model for \(p=1,000\) features that supports about \(20\) meaningful interactions of order \(r=500\).

  • Hence we can consider \(\alpha = 0.02\). With a model not involving hidden layers, we cannot specialize for an order of \(20\) interactions and thus we require a full model of order \(p^r/r! \approx 10^{365}\) parameters.

  • With s deep model we require about \(L=10 \approx \log_2 500\) layers and \(N = 4 \alpha p = 80\) neurons per layer, and thus the total number of parameters is at the order of \(LN^2 = 57,600\).

This deep construction which can capture a desired set of meaningful interactions is clearly more feasible and efficient than the shallow construction of astronomical size

The Back Propagation Algorithm

  • A key algorithm for learning parameters in deep learning models is the Back-Propagation algorithm to get the gradient of the loss function with respect to the parameter.

  • Back-Propagation implements backward mode automatic differentiation (see Chapter 4 of our book)

  • Key principle is to explain the chain-rule and to get a recursive expression for gradient flow.

  • A key elements are intermediate derivative values

\[ \delta^{[\ell]} := \frac{\partial C(a^{[L]}, y \, ; \, \theta) }{\partial z^{[\ell]}}, \qquad \ell = 1,\ldots,L, \]

Using the chain-rule: \[\begin{equation} \label{eqn:delta-recursion-manit} \delta^{[\ell]} = \frac{\partial a^{[\ell]}}{\partial z^{[\ell]}} \frac{\partial z^{[\ell+1]}}{\partial a^{[\ell]}} \frac{\partial C}{\partial z^{[\ell+1]}} =\frac{\partial a^{[\ell]}}{\partial z^{[\ell]}}\frac{\partial z^{[\ell+1]}}{\partial a^{[\ell]}} \delta^{[\ell+1]}, \qquad \ell = L-1,\ldots,1, \end{equation}\]

For the final layer, \(\ell = L\), we have, \[\begin{equation} \label{eqn:last-delta} \delta^{[L]} = \frac{\partial a^{[L]}}{\partial z^{[L]}}\frac{\partial C}{\partial a^{[L]}}. \end{equation}\]

  • Finally, uising \(z^{[\ell+1]}=W^{[\ell+1]}a^{[\ell]}+b^{[\ell+1]}\), we have \[ \frac{\partial z^{[\ell+1]}}{\partial a^{[\ell]}}={W^{[\ell+1]}}^\top. \]

Vanishing and Exploding Gradients

The key back propagation recursions:

  • forward step \(a^{[\ell+1]} = S^{[\ell]}\big(W^{[\ell]} a^{[\ell-1]} + b^{[\ell]}\big)\)
  • backward step \(\delta^{[\ell]} = \textrm{Diag}\big(\dot{\sigma}^{[\ell]}(z^{[\ell]})\big) {W^{[\ell+1]}}^\top \delta^{[\ell+1]},\)

From a practical perspective, these steps are sometimes subject to instability when the number of layers \(L\) is large.

  • By ignoring the activation functions and assuming that \(W^{[\ell]}\) is with a fixed square dimension and the same weight matrix W, for \(\ell = 1,\ldots,L-1\), and the last weight matrix is \(W^{[L]}\).

\[\begin{equation} \label{eq:repated-matrix-stuff} \hat{y}=a^{[L]}=W^{[L]} W^{[L-1]} W^{[L-2]}\cdot \ldots \cdot W^{[3]} W^{[2]} W^{[1]} x =W^{[L]} W^{L-1} x \end{equation}\] where \(W^{L-1}\) is the \(L-1\) power of \(W\).

  • Unless the maximal eigenvalues of \(W\) are exactly with a magnitude of unity, as \(L\) grows we have that \(\hat{y}\) either vanishess (towards \(0\)) or explodes (with values of increasing magnitude).

  • If \(W = wI\) (a constant multiple of the identity matrix), then \(\hat{y}=W^{[L]} w^{L-1} x\), and for any \(w \neq 1\), a vanishing \(\hat{y}\) or exploding \(\hat{y}\) phenomena persists.

  • This illustration shows that for non-small network depths (large \(L\)), instability issues may arise in the forward pass .

Same type of instability problem can then also persist in the backward pass since the backward recursion \(\delta^{[\ell]} =\textrm{Diag}\big(\dot{\sigma}^{[\ell]}(z^{[\ell]})\big) {W^{[\ell+1]}}^\top \delta^{[\ell+1]}\) also includes repeated matrix multiplications, and if for simplicity we ignore the activation functions and again take a constant matrix \(W\), then,

\[\begin{equation} \label{eq:back-matrix-power-problem} \delta^{[\ell]} = \Big(W^\top\Big)^{L-\ell} \delta^{[L]}. \end{equation}\]

  • There is often a vanishing or exploding nature of \(\delta^{[\ell]}\) for large \(L\) and low values of \(\ell\) (the first layers of the network).

  • The gradient values \(g_W^{[\ell]}\) and \(g_b^{[\ell]}\) may get smaller and smaller (vanishing) or larger and larger (exploding) as we go backward with every layer during back propagation.

  • In the worst case, vanishing gradients, may completely stop the neural network from training, or exploding gradients may throw parameter values towards arbitrary directions.

  • This may result in oscillations around the minima or even overshooting the optimum again and again.

  • Another impact of exploding gradients is that huge values of the gradients may cause number overflow resulting in incorrect computations or introductions of NAN (``not a number’’).

Solution:

  • Gradient descent improvements such as RMSProp, ADAM can help normalize such variation in the gradients. Nevertheless, numerical instability can still persist.

  • Further, with activation functions such as sigmoid or tanh, in cases of inputs far from \(0\) the gradient components of \(\textrm{Diag}\big(\dot{\sigma}^{[\ell]}(z^{[\ell]})\big)\) may also vanish.

  • Activation functions such as ReLU or Leaky ReLU handle such problems, yet the overarching phenomenon still persists.

  • One strategy for mitigating such a problem is based on weight intialization.

Weight Initilization

  • Starting with initial values that are either constant or \(0\) for the weights and bias parameters may throw the learning process off.

  • Such constant initial parameters may impose symmetry on the activation values of the hidden units and in turn prohibit the model from exploiting its expressive power.

  • Random initialization enables us to break any potential symmetries and is almost always preferable.

  • General practice: the most basic random intialization approach is to set all parameters of the weight matrices \(W^{[1]},\ldots,W^{[L]}\) as independent and identically distributed standard normal random variables and to set all the entries of the bias vectors \(b^{[1]},\ldots,b^{[L]}\) at \(0\).

  • A nice animation post on the influence of the weight initialization could be found here Initializing neural networks

  • For deep networks, heuristic to initialize the weights depending on the non-linear activation function are generally used. The most common practice is to draw the element of the matrix \(W^{[l]}\) from normal distribution with variance \(k/m_{l-1}\), where \(k\) depends on the activation function.

  • While these heuristics do not completely solve the exploding/vanishing gradients issue, they help mitigate it to a great extent.

  • for \(ReLU\) activation: \(k=2\)

  • for \(tanh\) activation: \(k=1\). The heuristic is called Xavier initialization.

  • Another commonly used heuristic is to draw from normal distribution with variance \(2/(m_{l-1}+m_l)\)

Batch Normalization

  • Batch normalization is to normalize (or standardize) not just the input data but also individual neuron values within the intermediate hidden layers or final layer of the network.

  • Taking \(j\) as an index of a neuron in layer \(\ell\), we may wish to have either \(z_j^{[\ell]}\) or \(a_j^{[\ell]}\) exhibit near-normalized values over the input dataset.

  • Such normalization of the neuron values then yields more consistent training and mitigates vanishing or exploding gradient problems. It also has a slight regularization effect which may prevent overfitting.

  • Here outlines normalization of the \(z_j^{[\ell]}\) values, but one may choose to do so for the \(a_j^{[\ell]}\) values instead.

Data Preprocessing

  • standardization of the data: subtraction of the mean of each feature and division by the standard deviation of the feature.

  • sample mean and sample standard deviation

\[\begin{equation} \label{eq:stats-mean-var} \overline{x}_i = \frac{1}{n} \sum_{j=1}^n x_i^{(j)}, \qquad s^2_i = \frac{1}{n} \sum_{j=1}^n (x_i^{(j)} - \overline{x}_i)^2. \end{equation}\]

  • standardization

\[\begin{equation} \label{eq:ref-stand-z} z^{(j)}_i = \frac{x^{(j)}_i - \overline{x}_i}{s_i} \qquad \text{for} \qquad j=1,\ldots,n. \end{equation}\]

  • For feature \(i\), \(z_i^{(1)}, \ldots,z_i^{(n)}\), has a sample mean of exactly \(0\) and a sample standard deviation of exactly \(1\).

  • Such standardization is useful as it places the dynamic range of the model inputs on a uniform scale and thus improves the numerical stability of algorithms.

Min-Max Normalization Technique

  • \(\color{Blue}{\textrm{Min-Max Normalization}}\) is an another preprocessing technique to scale numeric features between 0 and 1.

\[ z^{(j)}_i = \frac{x^{(j)}_i - x_i^{\text{min}}}{x_i^{\text{max}}-x_i^{\text{min}}} \qquad \text{for} \qquad j=1,\ldots,n. \]

where \(x_i^{\text{min}}\) is the minimum value of the \(i-th\) feature and \(x_i^{\text{max}}\) is the maximum value of the \(i-th\) feature

  • Scale Invariance: Preserves the shape of the original distribution.

  • Convergence: Helps in faster convergence during training.

  • Stability: Reduces the chance of vanishing or exploding gradients.

The Idea of Per Unit Normalization

  • The main idea of batch normalization is to consider neuron \(j\) in layer \(\ell\) and instead of using \(z^{[\ell]}_j\) to use a transformed version \(\tilde{z}_j^{[\ell]}\).

  • Such a transformation takes place both in training time and when using the model in production

  • The transformation aims to position the \(\tilde{z}_j^{[\ell]}\) values so that they have approximately zero mean and unit standard deviation over the data.

  • Further, the transformation involves a correction using trainable parameters.

  • During training time, at a given training epoch and for a given mini-batch of size \(n_b\):

\[\begin{equation} \label{eq-bm-mean-std} \hat{\mu}_j^{[\ell]} =\frac{1}{n_b}\sum_{i=1}^{n_b}z_j^{[\ell](i)} \qquad \text{and} \qquad \hat{\sigma}_j^{[\ell]} = \sqrt{ \frac{1}{n_b}\sum_{i=1}^{n_b}(z_j^{[\ell](i)}-\hat{\mu}_j^{[\ell]})^2}, \end{equation}\]

where \(z_j^{[\ell](i)}\) is the value at unit \(j\), at layer \(\ell\), and sample \(i\) within the mini-batch, prior to carrying out normalization.

  • With \(\hat{\mu}_j^{[\ell]}\) and \(\hat{\sigma}_j^{[\ell]}\) available, we compute

\[\begin{equation} \label{eq:bnorm1} \bar{z}_j^{[\ell](i)}=\frac{z_j^{[\ell](i)}-\hat{\mu}_j^{[\ell]}}{\sqrt{(\hat{\sigma}_j^{[\ell]})^2+\varepsilon}}, \end{equation}\]

  • At this point \(\bar{z}_j^{[\ell](i)}\) has nearly zero mean and nearly unit standard deviation for all data samples \(i\) in the mini-batch.

  • An additional transformation takes place in the form,

\[\begin{equation} \label{eq:bnorm2} \tilde{z}_j^{[\ell](i)}=\gamma_j^{[\ell]} \bar{z}_j^{[\ell](i)} + \beta_j^{[\ell]}, \end{equation}\]

where \(\gamma_j^{[\ell]}\) and \(\beta_j^{[\ell]}\) are trainable parameters.

  • \(\tilde{z}_j^{[\ell](i)}\) has a standard deviation of approximately \(\gamma_j^{[\ell]}\) and a mean of approximately \(\beta_j^{[\ell]}\) over the data samples \(i\) in the mini-batch.

  • These parameters are respectively initialized at \(1\) and \(0\), and then as training progresses, \(\gamma_j^{[\ell]}\) and \(\beta_j^{[\ell]}\) are updated using the same learning mechanisms applied to the weights and biases of the network.

Mitigating Overfitting with Dropout and Regularization

  • Dropout

  • Addition of Regularization Terms and Weight Decay

Dropout

Dropout is a popular and efficient regularization technique.

  • Dropout is a regularization technique where we during training randomly drop units.

  • The term dropout refers to dropping out units (hidden and visible) in a neural network.

  • By dropping a unit out, meaning temporarily removed it from the network, along with all its incoming and outgoing connections.

  • The choice of which units to drop is random.

  • At any back propagation iteration (forward pass and backward pass) on a mini-batch, only some random subset of the neurons is active. Practically neurons in layer \(\ell\), for \(\ell=0,\ldots,L-1\), have a specified probability \(p_{\text{keep}}^{[\ell]} \in (0,1]\) where if \(p_{\text{keep}}^{[\ell]} = 1\) dropout does not affect the layer, and otherwise each neuron \(i\) of the layer is ``dropped out’’ with probability \(1 - p_{\text{keep}}^{[\ell]}\).

  • In the backward pass: when neuron \(i\) is dropped out in layer \(\ell\), the weights \(w_{i,j}^{[\ell+1]}\) for all neurons \(j=1,\ldots,N_{\ell+1}\) are updated based on the gradient \([g_W^{[\ell]}]_{ij}\) which is set at \(0\).

  • With a pure gradient descent optimizer this means that weights \(w_{i,j}^{[\ell+1]}\) are not updated at all during the given iteration, whereas with a momentum based optimizer such as ADAM it means that the descent step for those weights has a smaller magnitude.

  • In practice, this simple and easy idea of dropout has improved performance of deep neural networks in many empirically tested cases. It is now an integral part of deep learning training.

Viewing Dropout as an Ensemble Approximation

  • Dropout can be viewed as an approximation of an \(\color{Red}{\textrm{ensemble method}}\), a general concept from machine learning.

  • What is \(\color{Red}{\textrm{ensemble learning}}\) ?

  • When we seek a model \(\hat{y} = f_\theta(x)\), we may use the same dataset to train multiple models that all try to achieve the same task.

  • We may then combine the models into an \(\color{Blue}{\textrm{ensemble}}\) (model).

  • The is usually \(\color{Red}{\textrm{more accurate}}\) than each of the individual models.

Ensemble learning

  • Consider a scalar output model.

  • We use \(\color{Blue}{M\ \textrm{models}}\): \(\hat{y}^{\{ i \} } = f_{\theta_{\{i\}}}^{\{i\}}(x)\) for \(i=1,\ldots,M\), where \(\theta_{\{i\}}\) is taken here as the set of parameters of the \(i\)-th model.

  • The ensemble model on an input \(x\) is then \(\color{Red}{\textrm{the average}}\), \[ f_{\theta}(x) = \frac{1}{M} \sum_{i=1}^M f_{\theta_{\{i\}}}^{\{ i\}}(x), \qquad \text{where} \qquad \theta = (\theta_{\{1\}},\ldots,\theta_{\{M\}}). \]

  • \(f_\theta(\cdot)\) is more computationally costly since it requires \(M\) models instead of a single model.

  • Nevertheless, there are benefits.

  • Assume the models are \(\color{Red}{\textrm{homogenous}}\) in nature and only differ due to randomness in the training process and not the model choice or hyper-parameters.

  • For some \(\color{Blue}{\textrm{fixed unseen input }\tilde{x}}\) we may treat the output of model \(i\), denoted \(\hat{y}^{\{i\}}_{\theta_{\{i\}}}(\tilde{x})\), as a \(\color{Red}{\textrm{random variable that is identical in distribution to every other model output } \hat{y}^{\{j\}}_{\theta_{\{j\}}}(\tilde{x})}\), yet generally not independent.

  • We further assume that any pair of model outputs is \(\color{Red}{\textrm{identically distributed to any other pair}}\): \[ \mathbb{E}\big[\hat{y}_{\theta_{\{i\}}}^{\{i\}}(\tilde{x})\big] = \mu, \qquad \textrm{Var}\big(\hat{y}_{\theta_{\{i\}}}^{\{i\}}(\tilde{x})\big) = \sigma^2, \qquad \text{and} \qquad \text{cor}\big(\hat{y}_{\theta_{\{i\}}}^{\{i\}}(\tilde{x}), \hat{y}_{\theta_{\{j\}}}^{\{j\}}(\tilde{x})\big) = \rho, \] where \(\text{cor}(\cdot, \cdot)\) is the correlation between two models \(i \neq j\) and is assumed to be the same for all \(i\), \(j\) pairs.

  • Such \(\color{Blue}{\textrm{an assumption on the correlation}}\) also imposes a lower bound on the correlation,

\[ -\frac{1}{M-1} \le \rho, \qquad \text{or} \qquad 0 \le \rho + \frac{1-\rho}{M}. \]

  • Now evaluate the \(\color{Blue}{\textrm{mean}}\) and \(\color{Blue}{\textrm{variance}}\) of the ensemble model:

\[ \mathbb{E}[f_{\theta}(\tilde{x})] = \frac{1}{M} \mathbb{E} \big[\sum_{i=1}^M f_{\theta_{\{i\}}}^{\{i\}}(\tilde{x}) \big] = \mu, \]

and further noting that \(\rho \sigma^2\) is the \(\color{Blue}{\textrm{covariance between any two models}}\) we obtain

\[ \textrm{Var}\big( f_{\theta}(\tilde{x}) \big) = \frac{1}{M^2} \textrm{Var}\Big(\sum_{i=1}^M f_{\theta_{\{i\}}}^{\{i\}}(\tilde{x})\Big) = \frac{1}{M^2} \big(M \sigma^2 + M(M-1) \rho \sigma^2 \big) = \Big( \rho + \frac{1-\rho}{M}\Big)\sigma^2. \]

  • As the number of models in the ensemble, \(M\), grows, the variance of the ensemble model \(\color{Blue}{\textrm{converges}}\) to \(\rho \sigma^2\).

  • Since \(\rho \le 1\) and practically \(\rho < 1\), this \(\color{Blue}{\textrm{limiting variance}}\) is less than \(\sigma^2\).

  • For example if \(\rho = 0.5\) as \(M\) grows the \(\color{Red}{\textrm{variance of the estimator drops}}\) by \(50\%\).

  • These properties of ensemble models make them \(\color{Blue}{\textrm{very attractive}}\) because the bias does not change but the variance decreases

  • Nevertheless, deep learning models \(\color{Red}{\textrm{are not easily amenable}}\) for ensemble models because the number of parameters and computational cost (both for training and production) is too high.

  • Training a single model may sometimes \(\color{Blue}{\textrm{take days and the computational costs of a single evaluation } f_{\theta_{\{i\}}}^{\{i\}}(\tilde{x})}\) are also non-negligible}.

  • This is where \(\color{Red}{\textrm{dropout comes in}}\).


Viewing Dropout as an Ensemble Approximation

We may \(\color{Red}{\textrm{loosely view dropout as an ensemble of } M \textrm{ models}}\) where \(M\) is the number of training iterations.

  • 1st iteration: Keep and update each unit with probability \(p\) (\(p_{keep}\)), drop remanding ones
  • 2st iteration: Keep and update another random selection of units, drop remanding units.
  • \(t\)th iter Continue in the same manner.
  • Test time Use all units. Weight multiplied by \(p\).

Viewing Dropout as an Ensemble Approximation

  1. For a neural network with \(M\) units there are \(2^M\) possible thinned neural networks. Consider this as our \(\color{Red}{\textrm{ensemble}}\).

  • \(\color{Blue}{\textrm{Approximation 1:}}\) At each iteration we sample one ensemble member and update it. Most of the networks will never be updated since \(2^M>>\) the number of iterations.

  1. At test time we would need to average over all \(2^M\) which is not feasible when \(2^M\) is huge.
  • \(\color{Blue}{\textrm{Approximation 2:}}\) Instead, at test time we evaluate the full neural network where the weight are multiplied by \(p\).

It has been empirically shown that this is a good approximation of the average of all ensemble members.

Addition of Regularization Terms and Weight Decay

  • Addition of a regularization term is another key approach to prevent overfitting and improve generalization performance.

  • Augmenting the loss with a regularization term \(R_\lambda(\theta)\) restricts the flexibility of the model, and this restriction is sometimes needed to prevent over-fitting.

  • In the context of deep learning, and especially when ridge regression style regularization is applied, this practice is sometimes called weight decay when considering gradient based optimization.

  • Take the original loss function \(C(\theta)\) and augment it to be \(\tilde{C}(\theta) = C(\theta) + R_\lambda(\theta)\).

  • Let us focus on the ridge regression type regularization with parameter \(\lambda >0\), and,

\[ R_\lambda(\theta) = \frac{\lambda}{2} R(\theta), \qquad \text{with} \qquad R(\theta) = \| \theta \|^2 = \theta_1^2 + \ldots + \theta_d^2. \]

  • Here for notational simplicity we simply consider all the \(d\) parameters of the model as scalars, \(\theta_i\) for \(i=1,\ldots,d\)

  • Now for simplicity, assume we execute basic gradient descent steps.

  • With a learning rate \(\alpha > 0\), the update at iteration \(t\) is,

\[ \theta^{(t+1)} = \theta^{(t)} - \alpha \nabla \tilde{C}(\theta^{(t)}). \]

In our ridge regression style penalty case we have \(\nabla \tilde{C}(\theta) = \nabla {C}(\theta) + \lambda \theta\), and hence the gradient descent update can be represented as

\[\begin{equation} \label{eq:weight-decay} \theta^{(t+1)} = (1- \alpha \lambda) \theta^{(t)} - \alpha \nabla {C}(\theta^{(t)}). \end{equation}\]

Assuming that \(\alpha \lambda < 2\), is that it involves shrinkage or weight decay directly on the parameters in addition to gradient based learning.

  • That is, independently of the value of the gradient \(\nabla {C}(\theta^{(t)})\), in every iteration, the update continues to decay the parameters, each time multiplying the previous parameter by a factor \(1-\alpha \lambda\).

Practice 4: In this practice, we mainly use the MNIST dataset to explore classification deep neural networks (DNN) models. At the end of this practice, you should be comfortable to use a software package (here keras) to run different models for a classification task. You will explore different models by exploring/tuning different hyperparamaters of the DNN:

  • number of layers and nodes
  • batch normalization
  • regularization technique
  • dropout
  • weight initialization

Deep Neural network: Practice 4 with Pyton (google collab)

Tutorial on MNIST data using R code (might be slow)

LS0tCnRpdGxlOiAiQSB3b3Jrc2hvcCBvbjogTWF0aGVtYXRpY2FsIEVuZ2luZWVyaW5nIG9mIERlZXAgTGVhcm5pbmcgLSBGb3VuZGF0aW9ucyIKc3VidGl0bGU6ICJEZWVwIE5ldXJhbCBOZXR3b3JrcyIKYXV0aG9yOgogIG5hbWU6IFByZXNlbnRlZCBieSBCZW5vaXQgTGlxdWV0CiAgYWZmaWxpYXRpb246IFVuaXZlcnNpdHkgb2YgUGF1IGV0IFBheXMgZGUgTCdBZG91cgogIGhlYWRlci1pbmNsdWRlczogXHVzZXBhY2thZ2V7YW1zbWF0aH0sXHVzZXBhY2thZ2V7eGNvbG9yfQpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIHRvYzogeWVzCiAgICB0b2NfZmxvYXQ6IHllcwogICAgdGhlbWU6IGNlcnVsZWFuCiAgICBoaWdobGlnaHQ6IGthdGUKICBwZGZfZG9jdW1lbnQ6CiAgICB0b2M6IHllcwotLS0KCiAKCmBgYHtyLGVjaG89RkFMU0V9CmNvbG9yaXplIDwtIGZ1bmN0aW9uKHgsIGNvbG9yKSB7CiAgaWYgKGtuaXRyOjppc19sYXRleF9vdXRwdXQoKSkgewogICAgc3ByaW50ZigiXFx0ZXh0Y29sb3J7JXN9eyVzfSIsIGNvbG9yLCB4KQogIH0gZWxzZSBpZiAoa25pdHI6OmlzX2h0bWxfb3V0cHV0KCkpIHsKICAgIHNwcmludGYoIjxzcGFuIHN0eWxlPSdjb2xvcjogJXM7Jz4lczwvc3Bhbj4iLCBjb2xvciwKICAgICAgeCkKICB9IGVsc2UgeAp9CmBgYAoKCi0gQmFzZWQgb24gICoqQ2hhcHRlcjEgYW5kIDUqKiBvZiB0aGUgYm9vayBbTWF0aGVtYXRpY2FsIEVuZ2luZWVyaW5nIG9mIERlZXAgTGVhcm5pbmddKGh0dHBzOi8vZGVlcGxlYXJuaW5nbWF0aC5vcmcvKSBieSBCZW5vaXQgTGlxdWV0LCBTYXJhdCBNb2thIGFuZCBZb25pIE5hemFyYXRoeSAKCi0gRGF0YSBhcmUgYXZhaWxhYmxlIGF0IDxodHRwczovL2dpdGh1Yi5jb20vYmVub2l0LWxpcXVldC9NQUVETD4KCgoKIyBJbnRyb2R1Y3Rpb24gdG8gRGVlcCBMZWFybmluZyAKCi0gUmVtaW5kZXIgYWJvdXQgTWFjaGluZSBMZWFybmluZyBodHRwczovL2dpdGh1Yi5jb20vYmVub2l0LWxpcXVldC9NQUVETC9NTAoKIyMgQSBGaXJzdCBEaXZlIEludG8gRGVlcCBMZWFybmluZwoKCi0gQSBkZWVwIGxlYXJuaW5nIG1vZGVsIGlzIGEgKip2ZXJ5IHZlcnNhdGlsZSBmdW5jdGlvbioqLCAkZl9cdGhldGEoeCkkLCB3aGVyZSB0aGUgaW5wdXQgJHgkIGlzIHR5cGljYWxseSBfKipoaWdoIGRpbWVuc2lvbmFsKipfIGFuZCB0aGUgcGFyYW1ldGVyICRcdGhldGEkIGlzIF8qKmhpZ2ggZGltZW5zaW9uYWwqKl8gYXMgd2VsbC4gCgotIFRoZSBmdW5jdGlvbiByZXR1cm5zIGFuIG91dHB1dCAkeT1mX1x0aGV0YSh4KSQsIHdoZXJlICR5JCBpcyB0eXBpY2FsbHkgb2YgYHIgY29sb3JpemUoInNpZ25pZmljYW50bHkgbG93ZXIgZGltZW5zaW9uIiwiYmx1ZSIpYCB0aGFuIGJvdGggJFx0aGV0YSQgYW5kICR4JC4gCgotIEdpdmVuIGByIGNvbG9yaXplKCJsYXJnZSBkYXRhc2V0cyIsInJlZCIpYCBjb21wcmlzZWQgb2YgbWFueSAkeCQgaW5wdXRzIG1hdGNoaW5nIGRlc2lyZWQgb3V0cHV0cyAkeSQsIGl0IGlzIG9mdGVuIHBvc3NpYmxlIHRvIGZpbmQgYHIgY29sb3JpemUoImdvb2QgcGFyYW1ldGVyIHZhbHVlcyIsInJlZCIpYCAkXHRoZXRhJCBzdWNoIHRoYXQgJGZfXHRoZXRhKFxjZG90KSQgYXBwcm94aW1hdGVseSBzYXRpc2ZpZXMgdGhlIGRlc2lyZWQgcmVsYXRpb25zaGlwIGJldHdlZW4gJHgkIGFuZCAkeSQuIAoKLSBUaGUgcHJvY2VzcyBvZiBmaW5kaW5nIHN1Y2ggJFx0aGV0YSQgaXMgY2FsbGVkICoqdHJhaW5pbmcqKi4gT25jZSB0cmFpbmVkLCB0aGUgbW9kZWwgJGZfXHRoZXRhKFxjZG90KSQgY2FuIGJlIGFwcGxpZWQgdG8gKip1bnNlZW4gaW5wdXQgZGF0YSoqLCAkeCQsIHdpdGggYSBob3BlIG9mIG1ha2luZyBnb29kICoqcHJlZGljdGlvbnMqKiwgKipjbGFzc2lmaWNhdGlvbnMqKiwgb3IgKipkZWNpc2lvbnMqKi4KCgotIGByIGNvbG9yaXplKCJJbnB1dCBkYXRhIiwiYmx1ZSIpYCAkeCQgY2FuIGJlIGluIHRoZSBmb3JtIG9mIGFuIF8qKmltYWdlKipfLCBfKip0ZXh0KipfLCBhIHNvdW5kIHdhdmVmb3JtLCB0YWJ1bGFyIGhldGVyb2dlbmVvdXMgZGF0YSwgb3Igc29tZSBvdGhlciB2YXJpYW50LiAKCi0gYHIgY29sb3JpemUoIk91dHB1dCBkYXRhIiwiYmx1ZSIpYCAkeSQgY2FuIGJlIHRoZSBfKipwcm9iYWJpbGl0eSBvZiBhIGxhYmVsKipfIGluZGljYXRpbmcgdGhlIG1lYW5pbmcvY29udGVudCBvZiB0aGUgaW1hZ2UsIGEgbnVtZXJpY2FsIHZhbHVlLCBvciBhbiBvYmplY3Qgb2Ygc2ltaWxhciBmb3JtIHRvICR4JCBzdWNoIGFzIHRyYW5zbGF0ZWQgdGV4dCBpbiBjYXNlICR4JCBpcyB0ZXh0LCBhIG1hc2tlZCBpbWFnZSBpbiBjYXNlICR4JCBpcyBhbiBpbWFnZSwgb3Igc2ltaWxhci4gCgojIyMgSW1hZ2VOZXQgYW5kIFZHRzE5Cgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgoKLSBPbmUgdmVyeSBwb3B1bGFyIHNvdXJjZSBvZiBkYXRhIGlzIHRoZSAqKkltYWdlTmV0IGRhdGFiYXNlKiogd2hpY2ggaGFzIGJlZW4gdXNlZCBmb3IgdGhlIGRldmVsb3BtZW50IGFuZCBiZW5jaG1hcmtpbmcgb2YgbWFueSBkZWVwIGxlYXJuaW5nIG1vZGVscy4KCi0gVGhlIGRhdGFiYXNlIGhhcyBuZWFybHkgYHIgY29sb3JpemUoIjE1IG1pbGxpb24gY29sb3IgaW1hZ2VzIiwicmVkIilgLiBJbiBtYW55IG1vZGVscyBhIHN1YnNldCBvZiBhYm91dCAkMS41JCBtaWxsaW9uIGltYWdlcyBhcmUgdXNlZCBmb3IgdHJhaW5pbmcgd2hlcmUgdGhlIGJhc2ljIGZvcm0gb2YgJHkkIGlzIGdpdmVuIGJ5IGEgbGFiZWwgd2hpY2ggaXMgb25lIG9mICQxLDAwMCQgY2F0ZWdvcmllcyBpbmRpY2F0aW5nIHRoZSBjb250ZW50IG9mIHRoZSBpbWFnZS4gCgotIENvbnNpZGVyIHRoZSAqKlZHRzE5IG1vZGVsKiogd2hpY2ggaXMgb25lIG9mIHNldmVyYWwgcG9wdWxhciAobm93IGNsYXNzaWNhbCkgZGVlcCBsZWFybmluZyBtb2RlbHMgZm9yIGltYWdlcy4gRm9yIHRoaXMgbW9kZWwsICR4JCBpcyBhICQyMjRcdGltZXMyMjQkIGNvbG9yIGltYWdlLiBJdCBpcyB0aHVzIGNvbXByaXNlZCBvZiAkM1x0aW1lczIyNFx0aW1lczIyNCA9IDE1MCw1MjgkIHZhbHVlcywgd2l0aCBldmVyeSBjb29yZGluYXRlIG9mICR4JCByZXByZXNlbnRpbmcgdGhlIGludGVuc2l0eSBvZiBhIHNwZWNpZmljIHBpeGVsIGNvbG9yIChgciBjb2xvcml6ZSgicmVkIiwicmVkIilgLCBgciBjb2xvcml6ZSgiZ3JlZW4iLCJncmVlbiIpYCwgb3IgYHIgY29sb3JpemUoImJsdWUiLCJibHVlIilgKS4gCgotIFRoZSBvdXRwdXQgJHkkIGlzIGEgJDEsMDAwJCBkaW1lbnNpb25hbCB2ZWN0b3Igd2hlcmUgZWFjaCBjb29yZGluYXRlIG9mIHRoZSB2ZWN0b3IgY29ycmVzcG9uZHMgdG8gYSBkaWZmZXJlbnQgdHlwZSBvZiBvYmplY3QsIGUuZy4gKipjYXIqKiwgKipiYW5hbmEqKiwgZXRjLiBUaGUgbnVtZXJpY2FsIHZhbHVlIG9mIHRoZSBjb29yZGluYXRlICR5X2kkLCB3aGVyZSBzYXkgJGkkIGlzIHRoZSBpbmRleCB3aGljaCBtYXRjaGVzICoqYmFuYW5hKiogaXMgdGhlIG1vZGVsJ3MgcHJlZGljdGlvbiBvZiB0aGUgcHJvYmFiaWxpdHkgdGhhdCB0aGUgaW5wdXQgJHgkIGlzIGEgcGljdHVyZSBvZiBhICoqYmFuYW5hKiouIAoKLSBJbiAyMDE0IHdoZW4gKipWR0cxOSoqIHdhcyBpbnRyb2R1Y2VkLCBpdCB3YXMgZmVkIGFib3V0ICQxLjUkIG1pbGxpb24gKipJbWFnZU5ldCoqIGltYWdlcywgJHgkLCBlYWNoIHdpdGggYSBjb3JyZXNwb25kaW5nIGxhYmVsLCBlLmcuICoqYmFuYW5hKiosIHdoaWNoIGlzIGVzc2VudGlhbGx5IGEgZGVzaXJlZCBvdXRwdXQgJHkkLiBUaGUgcHJvY2VzcyBvZiB0cmFpbmluZyAqKlZHRzE5KiogdGhlbiBpbnZvbHZlZCBmaW5kaW5nIGdvb2QgcGFyYW1ldGVycyAkXHRoZXRhJCBzbyB0aGF0IHdoZW4gdGhlIG1vZGVsIGlzIHByZXNlbnRlZCB3aXRoIGEgbmV3IHVuc2VlbiBpbWFnZSAkeCQsIGl0IHByZWRpY3RzIHRoZSBsYWJlbCBvZiB0aGUgaW1hZ2Ugd2VsbC4gCgotIE5vdGUgdGhhdCBpbiAqKlZHRzE5KiosICRcdGhldGEkIGhhcyBhIGh1Z2UgbnVtYmVyIG9mIHBhcmFtZXRlcnM7ICQxNDQkIG1pbGxpb24hIAoKPC9kaXY+CgohW10oZmFzdF9BSS5wbmcpCgoKLSAkMS41XHRpbWVzIDEwXjYkIGByIGNvbG9yaXplKCJpbnB1dCBkYXRhIiwiYmx1ZSIpYCBzYW1wbGVzIGVhY2ggb2Ygc2l6ZSBvZiBhYm91dCAkMS41XHRpbWVzMTBeNSQgKHBpeGVsIHZhbHVlcykuIAoKLSBUcmFpbmluZyBkYXRhIHNpemUgaGFzIGFib3V0ICQyLjI1IFx0aW1lcyAxMF57MTF9JCB2YWx1ZXMgKG51bWJlcnMpLiBUaGlzIGRhdGEgd2FzIHRoZW4gdXNlZCB0byBsZWFybiBhYm91dCAkMS40NFx0aW1lczEwXjgkIHBhcmFtZXRlcnMuIAoKLSBBdCB0aGUgdGltZSB3aGVuIHRoaXMgc3BlY2lmaWMgbW9kZWwgd2FzIGludHJvZHVjZWQgaXQgKip0b29rIGRheXMqKiB0byB0cmFpbiBhbmQgbXVjaCBsb25nZXIgdG8gZmluZSB0dW5lLiBUb2RheSBzdWNoIGEgbW9kZWwgbWF5IHRha2UgYXJvdW5kICoqOCBob3VycyB0byB0cmFpbioqLgoKLSBGdXJ0aGVyLCBpdCBjYW4gdGFrZSBhYm91dCAqKmEgZmlmdGhzIG9mIGEgc2Vjb25kKiogdG8gbWFrZSBhIHByZWRpY3Rpb24gd2l0aCB0aGlzIG1vZGVsLCB0aGF0IGlzIHRvIGV2YWx1YXRlICRmX1x0aGV0YSh4KSQuIAoKCi0gVGhpcyB0cmFpbmluZyBvZiBWR0cxOSBmcm9tIHNjcmF0Y2ggb24gYHIgY29sb3JpemUoIkltYWdlTmV0IiwiYmx1ZSIpYCBpcyBub3Qgc29tZXRoaW5nIG9uZSB3b3VsZCB0eXBpY2FsbHkgZG8gaW4gcHJhY3RpY2UuIAoKLSBXZSB1c2UgdGhlIGByIGNvbG9yaXplKCJwcmUtdHJhaW5lZCBWR0cxOSBtb2RlbCIsInJlZCIpYCBwYXJhbWV0ZXJzIGFuZCBhZGFwdCB0aGVtIGJhc2VkIG9uIGFub3RoZXIgZGF0YXNldCBjYWxsZWQgKipGcnVpdHMgMzYwKiogXGhyZWZ7aHR0cHM6Ly93d3cua2FnZ2xlLmNvbS9kYXRhc2V0cy9tb2x0ZWFuL2ZydWl0c30gd2hpY2ggaGFzIG5lYXJseSAkMTAwLDAwMCQgaW1hZ2VzIG9mIGZydWl0cwogCi0gSGVyZSB3ZSB1c2UgdGhlICoqZmFzdC5haSoqIGxpYnJhcnkgd2l0aCB0aGUgKipQeXRob24qKiBsYW5ndWFnZSB3aGljaCBhbHNvIHVzZXMgKipQeVRvcmNoKiogdW5kZXIgdGhlIGhvb2QuIAoKLSBIb3dldmVyLCB3ZSBjb3VsZCBoYXZlIHByZXNlbnRlZCBhbHRlcm5hdGl2ZXMgd2l0aCBvdGhlciBsYW5ndWFnZXMgKGUuZy4gSnVsaWEgb3IgUikgYXMgd2VsbCBhcyBvdGhlciBkZWVwIGxlYXJuaW5nIGxpYnJhcmllcyBzdWNoIGFzIGZvciBleGFtcGxlICoqS2VyYXMqKiB3aGljaCB1c2VzICoqVGVuc29yZmxvdyoqLiAgCgoKLSBjb2RlIGhlcmUgaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2RyaXZlLzFZT2pubEFxWTcxUHNwTG4wUXpvWWw1U21jRW1YcjRHUD91c3A9c2hhcmluZwoKCiMjIEJleW9uZCBDbGFzc2lmaWNhdGlvbgoKIVtdKGltYWdlX3Rhc2sucG5nKQoKLSBTZW1hbnRpYyBzZWdtZW50YXRpb24gb2YgaW1hZ2VzCgotIE9iamVjdCBkZXRlY3Rpb24gYW5kIGxvY2FsaXphdGlvbiAKCgojIyBBIFRhc3RlIG9mIFRhc2tzIGFuZCBBcmNoaXRlY3R1cmVzCgohW10oaW1hZ2VzL2FyY2hpY2hlY3R1cmUucG5nKQoKCgotIENvbnZvbHV0aW9uYWwgTmV1cmFsIE5ldHdvcmtzCgotIEdlbmVyYXRpdmUgQWR2ZXJzYXJpYWwgTmV0d29ya3MgKEdBTikKCi0gRW5jb2RlciwgRGVjb2RlciwgYW5kIEF1dG8tZW5jb2RlciBNb2RlbHMKCi0gUmVjdXJyZW50IE5ldXJhbCBOZXR3b3JrcywgTFNUTSwgYW5kIE90aGVyIFNlcXVlbmNlIE1vZGVscwoKLSBEZWVwIFJlaW5mb3JjZW1lbnQgTGVhcm5pbmcKCi0gRnVsbHkgQ29ubmVjdGVkIE5ldXJhbCBOZXR3b3JrcwoKLSBUcmFuc2Zvcm1lciBhcmNoaXRlY3R1cmVzCgotIERpZmZ1c2lvbiBtb2RlbHMKCgojIyBOZXVyYWwgTmV0d29ya3MgYXMgQXJ0aWZpY2lhbCBCcmFpbnM/CgohW10oQmlvbG9naWNhbF9uZXVyb24ucG5nKQoKPHN0eWxlPgpkaXYuYmx1ZSB7IGJhY2tncm91bmQtY29sb3I6I2U2ZjBmZjsgYm9yZGVyLXJhZGl1czogNXB4OyBwYWRkaW5nOiAyMHB4O30KPC9zdHlsZT4KPGRpdiBjbGFzcyA9ICJibHVlIj4KCi0gYHIgY29sb3JpemUoIkJyYWluIiwicmVkIilgOiBlc3RpbWF0ZWQgKio4NSBiaWxsaW9uIG5ldXJvbnMqKgoKLSBgciBjb2xvcml6ZSgiQSBzaW5nbGUgaHVtYW4gYWN0aW9uOiIsImJsdWUiKWAgIG1vdmVtZW50IG9mIGFuIGFybSBtYXkgaW5kdWNlIHRoZSBmaXJpbmcgb2YgYXJvdW5kICoqNTAgbWlsbGlvbioqIHN1Y2ggW25ldXJvbnNdKGh0dHBzOi8vZW4ud2lraXBlZGlhLm9yZy93aWtpL0Jpb2xvZ2ljYWxfbmV1cm9uX21vZGVsKSwgd2hlcmVhcyB0aGUgYHIgY29sb3JpemUoImlkZW50aWZpY2F0aW9uIG9mIGEgdmlzdWFsIiwicmVkIilgIG9iamVjdCBtYXkgdXNlIHRoZSBidWxrIG9mIGByIGNvbG9yaXplKCIxNTAgbWlsbGlvbiBuZXVyb25zIiwicmVkIilgIHRoYXQgYXJlIGluIHRoZSB2aXN1YWwgY29ydGV4LgoKLSAqKkRlZXAgbmV1cmFsIG5ldHdvcmsqKiBtb2RlbHMgYXJlIG5laXRoZXIgYnJhaW5zIG5vciBhdHRlbXB0cyB0byBjcmVhdGUgYXJ0aWZpY2lhbCBicmFpbnMuCgotIE5ldmVydGhlbGVzcywgdGhlIGRldmVsb3BtZW50IG9mIHRoZXNlIG1vZGVscyBpcyBgciBjb2xvcml6ZSgiaGlnaGx5IG1vdGl2YXRlZCIsInJlZCIpYCAgYnkgdGhlIGJpb2xvZ2ljYWwgc3RydWN0dXJlIG9mIHRoZSBicmFpbi4gCgotIFRoZSBiYXNpYyBidWlsZGluZyBibG9jayBvZiBhIGRlZXAgbmV1cmFsIG5ldHdvcmsgbW9kZWwgaXMgdGhlICoqKGFydGlmaWNpYWwpCm5ldXJvbioqIGFic3RyYWN0aW5nIHRoZSBgciBjb2xvcml6ZSgic3luYXBzZSBjb25uZWN0aW9uIiwicmVkIilgICBiZXR3ZWVuIG5ldXJvbnMgdmlhIGEgc2luZ2xlIG51bWJlciBjYWxsZWQgYW4KKiphY3RpdmF0aW9uIHZhbHVlKiouCgotIFBpb25lZXJpbmcgYW5kIGxhbmRtYXJrIHdvcmsgaW4gQUkgcmVzZWFyY2ggd2FzIGluc3BpcmVkIGJ5ICoqbmV1cm9zY2llbmNlKiouIFNpbmNlIGJyYWlucyBhcmUgZXNzZW50aWFsbHkgdGhlIG9ubHkgY29tcGxldGUgcHJvb2Ygd2UgaGF2ZSBmb3IgdGhlIGV4aXN0ZW5jZSBvZiB3aGF0IHdlIGNhbGwgJydnZW5lcmFsIGludGVsbGlnZW5jZScnCgo8L2Rpdj4KCgotIE1hbnkgdGFza3Mgb2YgZGVlcCBsZWFybmluZyBtb2RlbHMgaW52b2x2ZSB0aGUgYHIgY29sb3JpemUoIm1pbWlja2luZyBvZiBodW1hbiBsZXZlbCAgKG9yIGFuaW1hbCBsZXZlbCkgdGFza3MiLCJyZWQiKWAgIHN1Y2ggdW5kZXJzdGFuZGluZyBpbWFnZXMgb3IgY29udmVyc2F0aW9uYWwgdGFza3MuIAoKLSBUaGUgYHIgY29sb3JpemUoIm1vc3Qgd2VsbCBrbm93biBiZW5jaG1hcmtzIiwicmVkIilgIGluIHRoZSB3b3JsZCBvZiBhcnRpZmljaWFsIGludGVsbGlnZW5jZSBpcyB0aGUgW1R1cmluZyB0ZXN0XShodHRwczovL2VuLndpa2lwZWRpYS5vcmcvd2lraS9UdXJpbmdfdGVzdCksIG9yaWdpbmFsbHkgbmFtZWQgdGhlIGByIGNvbG9yaXplKCJpbWl0YXRpb24gZ2FtZSIsImJsdWUiKWAgd2hlbiBpbnRyb2R1Y2VkIGJ5IEFsYW4gVHVyaW5nIGluIDE5NTAuIAoKLSBJdCBpcyBlc3NlbnRpYWxseSBhIHRlc3QgdG8gc2VlIGByIGNvbG9yaXplKCJpZiBhIGNvbXB1dGVyIGNhbiBlbmdhZ2UgaW4gbG9uZyBjb252ZXJzYXRpb24gd2l0aCBhIGh1bWFuIiwiYmx1ZSIpYCwgYHIgY29sb3JpemUoIndpdGhvdXQgIGFub3RoZXIiLCJyZWQiKWAgb2JzZXJ2aW5nIGh1bWFuIGRpc3Rpbmd1aXNoaW5nIGJldHdlZW4gdGhlIGNvbXB1dGVyIGFuZCB0aGUgaHVtYW4uCgoKLSBgciBjb2xvcml6ZSgiRGVlcCBsZWFybmluZyBtb2RlbHMiLCJyZWQiKWAgZ2VuZXJhbGx5IG9ubHkgYWNoaWV2ZSBfbmFycm93XyB0YXNrcyBzdWNoIGFzICoqcGF0dGVybiByZWNvZ25pdGlvbioqLCBvciAqKnBsYXlpbmcgc3BlY2lmaWMgZ2FtZXMqKiwgYXMgb3Bwb3NlZCB0byBhcnRpZmljaWFsIGdlbmVyYWwgaW50ZWxsaWdlbmNlIChBR0kpIHRhc2tzIG9mIGNyZWF0aXZlIHByb2JsZW0gc29sdmluZy4KCgoKIyMgQSBGZXcgRXhhbXBsZSBQb3B1bGFyIERhdGFzZXRzCgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgotICoqTU5JU1QqKiBkaWdpdCBpbWFnZXM6IE9uZSBvZiB0aGUgbW9zdCBiYXNpYyBtYWNoaW5lIGxlYXJuaW5nIGRhdGFzZXRzIGlzIHRoZSBfTU5JU1QgZGF0YWJhc2VfLiBFYWNoICR4JCBpcyBhICQyOFx0aW1lcyAyOCQgYmxhY2sgYW5kIHdoaXRlIGltYWdlIG9mIGEgZGlnaXQsIGFuZCB3aGVuIHZlY3Rvcml6ZWQgaXQgaXMgYSAkcD03ODQkIGxvbmcgdmVjdG9yLiBFYWNoIGxhYmVsICR5JCBpcyBhbiBlbGVtZW50IGZyb20gKiowKiosKioxKiosICRcbGRvdHMkLCAqKjkqKiBpbmRpY2F0aW5nIHRoZSBkaWdpdC4gVGhlIGRpc3RyaWJ1dGlvbiBvZiBkaWdpdHMgaXMgX2JhbGFuY2VkXywgbWVhbmluZyB0aGF0IHRoZXJlIGFyZSBhcHByb3hpbWF0ZWx5IHRoZSBzYW1lIG51bWJlciBvZiBmaWd1cmVzIGZvciBlYWNoIGRpZ2l0IChjbGFzcykuIFRoZSBkYXRhc2V0IGlzIGJyb2tlbiBpbnRvIGEgdHJhaW4gc2V0IGNvbXByaXNlZCBvZiAkNjAsMDAwJCBpbWFnZXMsIGFuZCBhIHRlc3Qgc2V0IHdpdGggYW4gYWRkaXRpb25hbCAkMTAsMDAwJCBpbWFnZXMuICAKPC9kaXY+CgotICoqZmFzaGlvbiBNTklTVCoqIGRhdGFzZXQgd2hlcmUgdGhlIGltYWdlcyBhbmQgbGFiZWxzIGFyZSAqKnNoaXJ0KiosICoqc2hvZSoqLCBldGMuLi4gYW5kIHRoZSBkaW1lbnNpb25zIG9mIHRoZSBkYXRhIGFyZSBzZXQgZXhhY3RseSBsaWtlIE1OSVNULiBUaGUgZGlnaXQgTU5JU1QgY2FuIGJlIHRyYWluZWQgZm9yIG92ZXIgJDk5LjhcJSQgYWNjdXJhY3kgd2l0aCBzdGF0ZSBvZiB0aGUgYXJ0IG1vZGVscywgeWV0IGZhc2hpb24gTU5JU1QgaXMgc2xpZ2h0bHkgbW9yZSBjaGFsbGVuZ2luZyBhbmQgY2FuIGJlIHRyYWluZWQgdG8gYWNoaWV2ZSBhcm91bmQgJDk2LjVcJSQgYWNjdXJhY3kuCgoKPHN0eWxlPgpkaXYuYmx1ZSB7IGJhY2tncm91bmQtY29sb3I6I2U2ZjBmZjsgYm9yZGVyLXJhZGl1czogNXB4OyBwYWRkaW5nOiAyMHB4O30KPC9zdHlsZT4KPGRpdiBjbGFzcyA9ICJibHVlIj4KCi0gKipDSUZBUioqLTEwIHNtYWxsIGNvbG9yIGltYWdlczogU2ltaWxhcmx5IHRvIE1OSVNULCB0aGUgKipDSUZBUi0xMCBkYXRhc2V0KiogaGFzIHNtYWxsIGltYWdlcyAoJDMyXHRpbWVzMzIkIHBpeGVscyBpbiB0aGlzIGNhc2UpIGVhY2ggYnJva2VuIHVwIGludG8gJDEwJCBjbGFzc2VzLCBuYW1lbHkgKiphaXJwbGFuZSoqLCAqKmZyb2cqKiwgZXRjLi4uIEhvd2V2ZXIsIGhlcmUgdGhlIGltYWdlcyBhcmUgaW4gY29sb3IgYW5kIGhlbmNlICRwPTMgXHRpbWVzIDMyIFx0aW1lcyAzMiAgPSAzLDA3MiQuIEhlcmUgdGhlcmUgYXJlICQ1MCwwMDAkIHRyYWluIGltYWdlcyBhbmQgJDEwLDAwMCQgdGVzdCBpbWFnZXMgYW5kIGxpa2UgTU5JU1QgdGhpcyBpcyBhIGJhbGFuY2VkIGRhdGFzZXQgYmV0d2VlbiBjbGFzc2VzLiBUaGlzIGRhdGFzZXQgaXMgcG9wdWxhciB3aXRoIHJlc2VhcmNoIHBhcGVycyBhbmQgaXMgb2NjYXNpb25hbGx5IHVzZWQgYXMgYSBiZW5jaG1hcmsgZGF0YXNldC4gU3RhdGUgb2YgdGhlIGFydCBtb2RlbHMgYWNoaWV2ZSBhcm91bmQgJDk5XCUkIHByZWRpY3Rpb24gYWNjdXJhY3kuCjwvZGl2PgoKCiFbXShJbWFnZWRhdGEucG5nKQoKCgotICoqSW1hZ2VOZXQqKjogVGhlIF9JbWFnZU5ldCBkYXRhYmFzZV8gaXMgdW5kb3VidGVkbHkgYHIgY29sb3JpemUoInRoZSBtb3N0IHBvcHVsYXIgZGF0YXNldCIsInJlZCIpYCBmb3IgZGVlcCBsZWFybmluZyBiZW5jaG1hcmtpbmcsIGFzIHdlbGwgYXMgZm9yIGluZHVzdHJpYWwgdXNlIGluIGltYWdlIGFuYWx5c2lzLiBJdCB3YXMgY3JlYXRlZCBhdCB0aGUgZW5kIG9mIHRoZSBmaXJzdCBkZWNhZGUgb2YgdGhpcyBjZW50dXJ5IGFuZCBjb3VwbGVkIHdpdGggdGhlIGFzc29jaWF0ZWQgKipJbWFnZU5ldCBjaGFsbGVuZ2UqKi4gVGhlIGNoYWxsZW5nZSB1c2VzICQxLDAwMCQgbm9uLW92ZXJsYXBwaW5nIGltYWdlIGNsYXNzZXMuIGByIGNvbG9yaXplKCJJbWFnZU5ldCIsInJlZCIpYCBoYXMgbmVhcmx5IGByIGNvbG9yaXplKCIxNSBtaWxsaW9uIGNvbG9yIGltYWdlcyIsImJsdWUiKWAgd2l0aCB2YXJ5aW5nIHJlc29sdXRpb25zIGFuZCBhbiBhdmVyYWdlIHJlc29sdXRpb24gb2YgYXJvdW5kICQ0NzAgXHRpbWVzIDM4NSQuIFRoZSBpbWFnZXMgYXJlIGxhYmVsbGVkIHZlcnkgc3BlY2lmaWNhbGx5IHRvIG1vcmUgdGhhbiBgciBjb2xvcml6ZSgiMjAsMDAwIGNhdGVnb3JpZXMiLCJibHVlIilgIHdoZXJlIHNvbWUgaW1hZ2VzIGFyZSBhbHNvIGxhYmVsbGVkIHdpdGggYm91bmRpbmcgYm94ZXMgYXJvdW5kIG9iamVjdHMuCgo8c3R5bGU+CmRpdi5vcmFuZ2UgeyBiYWNrZ3JvdW5kLWNvbG9yOiNGRkQ1ODA7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+Cgo8ZGl2IGNsYXNzID0gIm9yYW5nZSI+Ci0gSW4gMjAxMiB0aGUgYHIgY29sb3JpemUoIkFsZXhOZXQgZGVlcCBsZWFybmluZyBtb2RlbCIsInJlZCIpYCBhY2hpZXZlZCBhIHRvcC01IGFjY3VyYWN5IG9mIG5lYXJseSAkODVcJSQsIG1lYW5pbmcgdGhhdCBpbiAkODVcJSQgb2YgdGhlIHRlc3RlZCBpbWFnZXMgdGhlIGNvcnJlY3QgbGFiZWwgb3V0IG9mIHRoZSAkMSwwMDAkIGxhYmVscyBpcyBvbmUgb2YgdGhlIHRvcC01IHByZWRpY3RlZCBwcm9iYWJpbGl0aWVzLiBCeSB0b2RheSwgc3RhdGUgb2YgdGhlIGFydCBwZXJmb3JtYW5jZSBmb3IgdG9wLTUgYWNjdXJhY3kgaXMgb3ZlciAkOTlcJSQgYW5kIGZvciB0b3AtMSAob3IgYWJzb2x1dGUgYWNjdXJhY3kpIHN0YXRlIG9mIHRoZSBhcnQgaXMganVzdCBvdmVyICQ5MFwlJC4KPC9kaXY+Cgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgoKLSAqKklNRGIqKi4gVGhpcyAgYHIgY29sb3JpemUoInRleHR1YWwgZGF0YXNldCIsInJlZCIpYCBoYXMgJDUwLDAwMCQgbW92aWUgcmV2aWV3cy4gSXQgaXMgc3BsaXQgaW50byAkMjUsMDAwJCBmb3IgdHJhaW5pbmcgYW5kIHRoZSByZW1haW5kZXIgZm9yIHRlc3RpbmcuIEZvciBlYWNoIHJldmlldyBhbiBpbmRpY2F0aW9uIG9mIGVpdGhlciBgciBjb2xvcml6ZSgicG9zaXRpdmUiLCJibHVlIilgIG9yIGByIGNvbG9yaXplKCJuZWdhdGl2ZSIsImJsdWUiKWAgaW5kaWNhdGVzIHRoZSBzZW50aW1lbnQgb2YgdGhlIHJldmlldy4gVGhlIHR5cGljYWwgdGFzayB3aXRoIHN1Y2ggYSBkYXRhc2V0IGlzIHRvIHByZWRpY3QgdGhlIHNlbnRpbWVudCBmb3IgYW4gdW5zZWVuIHJldmlldy4gVGhpcyBkYXRhc2V0IGlzIG9mdGVuIHVzZWQgYXMgYSBwcmFjdGljZSBkYXRhc2V0IGZvciBpbnRyb2R1Y3RvcnkgKipuYXR1cmFsIGxhbmd1YWdlIHByb2Nlc3NpbmcgKE5MUCkqKi4KPC9kaXY+CgoKIyMgRGF0YTogU2VlbiwgVW5zZWVuLCBUcmFpbmluZywgVGVzdGluZwogICAgCi0gRGlzdGluZ3Vpc2ggYmV0d2VlbiAqKnNlZW4gZGF0YSoqIGFuZCAqKnVuc2VlbiBkYXRhKiouCgotICoqU2VlbioqIGRhdGEgaXMgYXZhaWxhYmxlIGZvciBsZWFybmluZywgbmFtZWx5IGZvciB0cmFpbmluZyBvZiBtb2RlbHMsIG1vZGVsIHNlbGVjdGlvbiwgcGFyYW1ldGVyIHR1bmluZywgYW5kIHRlc3Rpbmcgb2YgbW9kZWxzLiAKCgotICoqVW5zZWVuKiogZGF0YSBpcyBlc3NlbnRpYWxseSB1bmxpbWl0ZWQuIEFsbCBkYXRhIGZyb20gdGhlIHJlYWwgd29ybGQgdGhhdCBpcyBub3QgYXZhaWxhYmxlIHdoaWxlIGxlYXJuaW5nIHRha2VzIHBsYWNlIGJ1dCBpcyBsYXRlciBhdmFpbGFibGUgd2hlbiB0aGUgbW9kZWwgaXMgdXNlZC4gVGhpcyBjYW4gYmUgZGF0YSBmcm9tIHRoZSBmdXR1cmUsIG9yIGRhdGEgdGhhdCB3YXMgbm90IGNvbGxlY3RlZCBvciBsYWJlbGxlZCB3aXRoIHRoZSBzZWVuIGRhdGEuIAoKCiFbXSguLi9EYXRhLnBuZykKCgoKIyBGZWVkLWZvcndhcmQgZGVlcCBuZXVyYWwgbmV0d29yayAKCi0gTGV0IHN0YXJ0IHdpdGggYSBzaW1wbGVzdCBuZXVyYWwgbmV0d29yawoKIyMgQSBTaGFsbG93IE5ldXJhbCBOZXR3b3JrOiBzaWdtb2lkIG1vZGVsCgotIEEgbWFwIGZyb20gJHhcaW4gXFJlXnAkIHRvICR5XGluXHswLDFcfSQuCgohW10oLi4vbG9naXN0aWNfYXJjaGl0ZWN0dXJlLnBuZykKCi0gSW5wdXQgdG8gb3V0cHV0Ci0gSW5wdXQgJHgkIGlzIHRoZSBmZWF0dXJlcyBhbmQgJHkkIGlzIHRoZSByZXNwb25zZQoKKipTaWdtb2lkIG1vZGVsIGlzIGEgbG9naXN0aWMgbW9kZWwqKiAKCkl0IGdpdmVzIGEgcHJvYmFiaWxpdHkgYXMgb3V0cHV0OgoKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6Zmlyc3Qtc2hhbGxvdy12aWV3fQpcaGF0e3l9PVx1bmRlcmJyYWNle1xzaWdtYVxsZWZ0KFxvdmVyYnJhY2V7Yit3Xlx0b3AgIHh9Xnt6fVxyaWdodCl9X3thfS4KXGVuZHtlcXVhdGlvbn0KCgoKIyMjIEV4YW1wbGUgb2YgZGF0YSBmb3Igc3VjaCBtb2RlbDogCgpDb25zaWRlciB0aGUgZGF0YSBmcm9tIF9CcmVhc3QgQ2FuY2VyIFdpc2NvbnNpbiAoRGlhZ25vc3RpYylfIChXQkNEKSBbZGF0YXNldF0oaHR0cHM6Ly9hcmNoaXZlLmljcy51Y2kuZWR1L21sL2RhdGFzZXRzL0JyZWFzdCtDYW5jZXIrV2lzY29uc2luKyUyOERpYWdub3N0aWMlMjkpCgotIEdvYWwgaXMgdG8gcHJlZGljdCBpZiBhIGBgYmVuaWduYGAgKFkgPSAwKSBvciBhIGBgbWFsaWduYW50YGAgKFkgPSAxKSBsdW1wcyBvZiBhIGJyZWFzdCBtYXNzLgoKLSBIaXN0b3JpY2FsIGRhdGEgY29uc2lzdHMgb2YgYSBsYXJnZSBudW1iZXIgb2YgcGF0aWVudCByZWNvcmRzCgotIDMwICg9JGQkKSBjaGFyYWN0ZXJpc3RpY3Mgb2YgaW5kaXZpZHVhbCBjZWxscyBvZiBicmVhc3QgY2FuY2VyCgotICFbXSguLi9icmVhc3QucG5nKQoKLSBgciBjb2xvcml6ZSgiVXNlIGEgbWFjaGluZSBsZWFybmluZyBhbGdvcml0aG0iLCJyZWQiKWAgdGhhdCBvYnNlcnZlcyB0aGVzZSBkYXRhLCBwcm9kdWNlcyBhIHByZWRpY3RvcgoKLSBQcmVkaWN0b3IgdGFrZXMgYXMgaW5wdXQgMzAgdmFsdWVzLCByZXR1cm5zIGEgc2luZ2xlIEJvb2xlYW4gcHJlZGljdGlvbiAoYGAwYGAgb3IgYGAxYGApCgotIFRoaXMgaXMgYSAqKmNsYXNzaWZpZXIqKiwgc2luY2Ugd2UgYXJlIHByZWRpY3RpbmcgYW4gb3V0Y29tZSB0aGF0IHRha2VzIG9ubHkgdHdvIHZhbHVlcwoKLSBFdmFsdWF0ZSBtb2RlbCBieSBpdHMgZXJyb3IgcmF0ZSBvbiBhICoqc2VwYXJhdGUgdGVzdCBzZXQgb2YgZGF0YSoqLCBub3QgdXNlZCB0byBkZXZlbG9wIHRoZSBtb2RlbAoKLSBBICoqcHJvYmFiaWxpc3RpYyBtb2RlbCoqIHJldHVybnMgYSBfcHJvYmFiaWxpdHlfIHRoYXQgdGhlIHBhdGllbnQgaGFzIGEgbWFsaWduYW50IGx1bXAsIG5vdCBqdXN0IGEgQm9vbGVhbgoKCiMjIyBNdWx0aS1jbGFzc2lmaWNhdGlvbiB0YXNrOiBTb2Z0bWF4IG1vZGVsCgohW10oaW1hZ2VzL3NvZnRtYXhfbGF5ZXIucG5nKQoKLSBTb2Z0bWF4OgoKXFsKIFxoYXR7eX09XHVuZGVyYnJhY2V7U197XHRleHR7c29mdG1heH19IFxiaWcoXG92ZXJicmFjZXtiK1cgIHh9Xnt6XGluIHtcbWF0aGJiIFJ9Xkt9XGJpZyl9X3thXGluIHtcbWF0aGJiIFJ9Xkt9LApccXF1YWQKXHRleHR7d2l0aH0KXHFxdWFkClNfe1x0ZXh0cm17U29mdG1heH19KHopID0gXGZyYWN7MX17XHN1bV97aT0xfV57S30gZV57el9pfX0gClxiZWdpbntibWF0cml4fQplXnt6XzF9IFxcClx2ZG90c1xcCmVee3pfe0t9fVxcClxlbmR7Ym1hdHJpeH0uClxdCgoKCgoKIyMgVGhlIEdlbmVyYWwgRnVsbHkgQ29ubmVjdGVkIEFyY2hpdGVjdHVyZQoKIVtdKHNpbmdsZV9oaWRkZW4ucG5nKQoKIVtdKGltYWdlcy9kZWVwX25ldXJhbF9uZXR3b3JrMS5wbmcpCgojIyMgKipBIE1vZGVsIEJhc2VkIG9uIEZ1bmN0aW9uIENvbXBvc2l0aW9uKioKClRoZSBnb2FsIG9mIGEgZmVlZGZvcndhcmQgbmV0d29yayBpcyB0byBhcHByb3hpbWF0ZSBzb21lIGZ1bmN0aW9uICRmXio6IFxtYXRoYmJ7Un1ecCBcbG9uZ3JpZ2h0YXJyb3cgXG1hdGhiYntSfV5xJC4gQSBmZWVkZm9yd2FyZCBuZXR3b3JrIG1vZGVsIGRlZmluZXMgYSBtYXBwaW5nICRmX1x0aGV0YTogXG1hdGhiYntSfV5wIFxsb25ncmlnaHRhcnJvdyBcbWF0aGJie1J9XnEkIGFuZCBsZWFybnMgdGhlIHZhbHVlIG9mIHRoZSBwYXJhbWV0ZXJzICRcdGhldGEkIHRoYXQgaWRlYWxseSByZXN1bHQgaW4KCiQkCmZeKih4KSBcYXBwcm94IGZfXHRoZXRhKHgpCiQkCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgpUaGUgZnVuY3Rpb24gJGZfXHRoZXRhJCBpcyByZWN1cnNpdmVseSBjb21wb3NlZCB2aWEgYSBjaGFpbiBvZiBmdW5jdGlvbnM6CgpcW2ZfXHRoZXRhKHgpPWZfe1x0aGV0YVtMXX1ee1tMXX1cbGVmdChmX3tcdGhldGFbTC0xXX1ee1tMLTFdfVxsZWZ0KFxsZG90c1xsZWZ0KGZfe1x0aGV0YVsxXX1ee1sxXX0oeClccmlnaHQpIFxsZG90c1xyaWdodClccmlnaHQpXF0KCi0gJGZfe3tcbWF0aGJie1x0aGV0YX19XntbXGVsbF19fV57KFxlbGwpfToge1xtYXRoYmIgUn1ee05fe1xlbGwtMX19IFxsb25ncmlnaHRhcnJvdyB7XG1hdGhiYiBSfV57Tl9cZWxsfSQgZm9yIHRoZSAkXGVsbCR0aCBsYXllciBhc3NvY2lhdGVkIHBhcmFtZXRlcnMgJHtcbWF0aGJie1x0aGV0YX19XntbXGVsbF19IFxpbiBcVGhldGFee1tcZWxsXX0kLCB3aGVyZSAkXFRoZXRhXntbXGVsbF19JCBpcyB0aGUgcGFyYW1ldGVyIHNwYWNlIGZvciB0aGUgJFxlbGwkdGggbGF5ZXIuIAoKLSBUaGUgXHRleHRpdHtkZXB0aH0gb2YgdGhlIG5ldHdvcmsgaXMgJEwkLiAKLSAkTl8wID0gcCQgKHRoZSBudW1iZXIgb2YgZmVhdHVyZXMpIGFuZCAkTl9MID0gcSQgKHRoZSBudW1iZXIgb2Ygb3V0cHV0IHZhcmlhYmxlcykuIAotIFRoZSBudW1iZXIgb2YgbmV1cm9ucyBpbiB0aGUgbmV0d29yayBpcyAkXHN1bV97XGVsbD0xfV5MIE5fXGVsbCQuCjwvZGl2PgoKIyMjICpBZmZpbmUgVHJhbnNmb3JtYXRpb25zIEZvbGxvd2VkIGJ5IEFjdGl2YXRpb25zKgoKCi0gYHIgY29sb3JpemUoIlRoZSBmdW5jdGlvbiIsInJlZCIpYCAgJGZfe3tcbWF0aGJie1x0aGV0YX19XntbXGVsbF19fV57KFxlbGwpfSQgIGlzICBkZWZpbmVkIGJ5IGFuIGByIGNvbG9yaXplKCJhZmZpbmUgdHJhbnNmb3JtYXRpb24gIiwiYmx1ZSIpYCBmb2xsb3dlZCBieSBgciBjb2xvcml6ZSgiYW4gYWN0aXZhdGlvbiBmdW5jdGlvbiAiLCJibHVlIilgLiAKCi0gQWN0aXZhdGlvbiBmdW5jdGlvbnMgYXJlIHRoZSBtZWFucyBvZiBpbnRyb2R1Y2luZyBgciBjb2xvcml6ZSgibm9uLWxpbmVhcml0eSIsInJlZCIpYCBpbnRvIHRoZSBtb2RlbC4KCi0gVGhlIG91dHB1dCBvZiBsYXllciAkXGVsbCQgaXMgcmVwcmVzZW50ZWQgYnkgdGhlIHZlY3RvciAkYV57W1xlbGxdfSQgCgotIGByIGNvbG9yaXplKCJUaGUgaW50ZXJtZWRpYXRlIHJlc3VsdCAiLCJibHVlIilgIG9mIHRoZSBhZmZpbmUgdHJhbnNmb3JtYXRpb24gaXMgcmVwcmVzZW50ZWQgYnkgdGhlIHZlY3RvciAkel57W1xlbGxdfSQgCgotIFdlIGRlbm90ZSB0aGUgb3V0cHV0IG9mIHRoZSBtb2RlbCB2aWEgJFxoYXR7eX0kIGFuZCBoZW5jZSwKXFsKXGhhdHt5fSA9IGFee1tMXX0gPSBmX1x0aGV0YSh4KS4KXF0KClRoZSBhY3Rpb24gb2YgJGZfe3tcbWF0aGJie1x0aGV0YX19XntbXGVsbF19fV57KFxlbGwpfSQgaXMgcmVwcmVzZW50ZWQgYXMgCgoKIVtdKG1hcHBpbmcucG5nKQoKCi0gJGFee1swXX0gPSB4JC4gVGhlIHBhcmFtZXRlcnMgb2YgdGhlICRcZWxsJHRoIGxheWVyLCAkXG1hdGhiYntcdGhldGF9XntbXGVsbF19JCwgYXJlIGdpdmVuIGJ5OgotICROX3tcZWxsfSBcdGltZXMgTl97XGVsbC0xfSQgKip3ZWlnaHQgbWF0cml4KiogJFdee1tcZWxsXX0gPSBcYmlnKHdee1tcZWxsXX1fe2ksan1cYmlnKSQgCi0gJE5fXGVsbCQgZGltZW5zaW9uYWwgKipiaWFzIHZlY3RvcioqICRiXntbXGVsbF19ID0gKGJfaV57W1xlbGxdfSkkLiAKLSBUaHVzIHRoZSBwYXJhbWV0ZXIgc3BhY2Ugb2YgdGhlIGxheWVyIGlzICRcVGhldGFee1tcZWxsXX0gPSBcUmVee05fe1xlbGx9IFx0aW1lcyBOX3tcZWxsLTF9fSBcdGltZXMgXFJlXntOX3tcZWxsfX0kLgoKLSBUaGUgYWN0aXZhdGlvbiBmdW5jdGlvbiAkU157W1xlbGxdfTogXFJlXntOX3tcZWxsfX0gXGxvbmdyaWdodGFycm93IFxSZV57Tl97XGVsbH19JCBpcyBgciBjb2xvcml6ZSgiYSBub25saW5lYXIgbXVsdGl2YWx1ZWQgZnVuY3Rpb24gIiwicmVkIilgLiAKRm9yICRcZWxsID0gMSxcbGRvdHMsTC0xJCBpdCBpcyBnZW5lcmFsbHkgb2YgdGhlIGZvcm0gClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxbjphY3RpdmF0aW9uLWZ1bmN0aW9uLXZlY3Rvcn0KU157W1xlbGxdfSh6KT1cbGVmdFtcc2lnbWFee1tcZWxsXX1cbGVmdCh6X3sxfVxyaWdodCkgfiBcbGRvdHMgflxzaWdtYV57W1xlbGxdfVxsZWZ0KHpfe05fe1xlbGx9fVxyaWdodClccmlnaHRdXntcdG9wfSwlICBcICBcICBcICBcZWxsPTEsXGxkb3RzLEwtMSwKXGVuZHtlcXVhdGlvbn0KCndoZXJlICRcc2lnbWFee1tcZWxsXX06IFxSZSBcdG8gXFJlJCBpcyAgdHlwaWNhbGx5IGFuIGFjdGl2YXRpb24gZnVuY3Rpb24gIGByIGNvbG9yaXplKCJjb21tb24gdG8gYWxsIGhpZGRlbiBsYXllcnMiLCJyZWQiKWAuIEZvciB0aGUgb3V0cHV0IGxheWVyLCAkXGVsbCA9IEwkLCBpdCBpcyBvZnRlbiBvZiBhIGRpZmZlcmVudCBmb3JtIGRlcGVuZGluZyBvbiB0aGUgdGFzayBhdCBoYW5kLiAKCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgpPdXRwdXQgbGF5ZXIsICRcZWxsID0gTCQKCi0gRm9yIF9tdWx0aS1jbGFzc18gY2xhc3NpZmljYXRpb246IGEgKipzb2Z0bWF4KiogZnVuY3Rpb24gaXMgdXNlZCwgZm9yIGJpbmFyeSBjbGFzc2lmaWNhdGlvbiwgYSAqKnNpZ21vaWQqKiAKLSBUaGUgb3V0cHV0IG9mIHRoZSBuZXR3b3JrIGlzIGEgdmVjdG9yIG9mIHByb2JhYmlsaXR5IHZhbHVlcyBkZXRlcm1pbmluZyBjbGFzcyBtZW1iZXJzaGlwLiAKLSBJbiBvcmRlciB0byBnZXQgYSBjbGFzcyBsYWJlbCBwcmVkaWN0aW9uIG9uZSBjYW4gY29udmVydCB0aGUgcHJlZGljdGVkIHByb2JhYmlsaXR5IHNjb3JlcyBpbnRvIGEgY2xhc3MgbGFiZWwgdXNpbmcgYSBjaG9zZW4gKip0aHJlc2hvbGQqKiwgb3IgbW9yZSBzaW1wbHkgKiptYXhpbXVtIGEgcG9zdGVyaW9yaSBwcm9iYWJpbGl0eSoqCjwvZGl2PgoKIyMjIE9uZSBMYXllciBOTgoKIVtdKGltYWdlcy9PbmVfaGlkZGVuX2xheWVyLnBuZykKCiFbXShpbWFnZXMvbm90YXRpb25faW1hZ2VfMUwucG5nKQoKCiQkXGxlZnRcewpcYmVnaW57ZXFuYXJyYXkqfQpcY29sb3J7R3JlZW59IHt6XzFee1sxXX0gfSAmPSYgXGNvbG9ye09yYW5nZX0ge3dfMV57WzFdfX0gXlQgXGNvbG9ye1JlZH14ICsgXGNvbG9ye0JsdWV9IHtiXzFee1sxXX0gfSAgIFxoc3BhY2V7MmNtfVxjb2xvcntQdXJwbGV9IHthXzFee1sxXX19ID0gXHNpZ21hKCBcY29sb3J7R3JlZW59IHt6XzFee1sxXX19IClcXApcY29sb3J7R3JlZW59IHt6XzJee1sxXX0gfSAmPSYgXGNvbG9ye09yYW5nZX0ge3dfMl57WzFdfX0gXlQgXGNvbG9ye1JlZH14ICsgXGNvbG9ye0JsdWV9IHtiXzJee1sxXX0gfSBcaHNwYWNlezJjbX0gXGNvbG9ye1B1cnBsZX0ge2FfMl57WzFdfX0gPSBcc2lnbWEoIFxjb2xvcntHcmVlbn0ge3pfMl57WzFdfX0gKVxcClxjb2xvcntHcmVlbn0ge3pfM157WzFdfSB9ICY9JiBcY29sb3J7T3JhbmdlfSB7d18zXntbMV19fSBeVCBcY29sb3J7UmVkfXggKyBcY29sb3J7Qmx1ZX0ge2JfM157WzFdfSB9IFxoc3BhY2V7MmNtfSBcY29sb3J7UHVycGxlfSB7YV8zXntbMV19fSA9IFxzaWdtYSggXGNvbG9ye0dyZWVufSB7el8zXntbMV19fSApXFwKXGNvbG9ye0dyZWVufSB7el80XntbMV19IH0gJj0mIFxjb2xvcntPcmFuZ2V9IHt3XzRee1sxXX19IF5UIFxjb2xvcntSZWR9eCArIFxjb2xvcntCbHVlfSB7Yl80XntbMV19IH0gXGhzcGFjZXsyY219IFxjb2xvcntQdXJwbGV9IHthXzRee1sxXX19ID0gXHNpZ21hKCBcY29sb3J7R3JlZW59IHt6XzRee1sxXX19ICkKXGVuZHtlcW5hcnJheSp9XHJpZ2h0LiQkCgp3aGVyZSAkeD0oeF8xLHhfMix4XzMpXlQkIGFuZCAkd19qXntbMV19PSh3X3tqLDF9XntbMV19LHdfe2osMn1ee1sxXX0sd197aiwzfV57WzFdfSx3X3tqLDR9XntbMV19KV5UJCAoZm9yICRqPTEsXGxkb3RzLDQkKS4gCgpUaGVuLCB0aGUgb3V0cHV0IGxheWVyIGlzIGRlZmluZWQgYnk6CgoKXGJlZ2lue2VxbmFycmF5Kn0KXGNvbG9ye0dyZWVufSB7el8xXntbMl19IH0gJj0mIFxjb2xvcntPcmFuZ2V9IHt3XzFee1syXX19IF5UIFxjb2xvcntwdXJwbGV9YV57WzFdfSArIFxjb2xvcntCbHVlfSB7Yl8xXntbMl19IH0gICBcaHNwYWNlezJjbX1cY29sb3J7UHVycGxlfSB7YV8xXntbMl19fSA9IFxzaWdtYSggXGNvbG9ye0dyZWVufSB7el8xXntbMl19fSApXFwKXGVuZHtlcW5hcnJheSp9Cgp3aGVyZSAkYV57WzFdfT0oYV57WzFdfV8xLFxsZG90cyxhXntbMV19XzQpXlx0b3AkIGFuZCAkd18xXntbMl19PSh3X3sxLDF9XntbMl19LHdfezEsMn1ee1syXX0sd197MSwzfV57WzJdfSx3X3sxLDR9XntbMl19KV5cdG9wJCAKCgpPbmUgY2FuIHVzZSAqKm1hdHJpeCByZXByZXNlbnRhdGlvbioqIGZvciBlZmZpY2llbmN5IGNvbXB1dGF0aW9uOgoKXGJlZ2lue2VxdWF0aW9ufSBcYmVnaW57Ym1hdHJpeH0gIFxjb2xvcntPcmFuZ2V9LSAmIFxjb2xvcntPcmFuZ2V9IHt3XzFee1sxXX0gfV5UICAmIFxjb2xvcntPcmFuZ2V9LVxcIFxjb2xvcntPcmFuZ2V9LSAgJiBcY29sb3J7T3JhbmdlfSB7d18yXntbMV0gfSB9IF5UICYgXGNvbG9ye09yYW5nZX0tIFxcIFxjb2xvcntPcmFuZ2V9LSAmIFxjb2xvcntPcmFuZ2V9IHt3XzNee1sxXX0gfV5UICAgICYgXGNvbG9ye09yYW5nZX0tIFxcIFxjb2xvcntPcmFuZ2V9LSAmIFxjb2xvcntPcmFuZ2V9IHt3XzRee1sxXX0gfV5UICAmIFxjb2xvcntPcmFuZ2V9LSBcZW5ke2JtYXRyaXh9ICAgXGJlZ2lue2JtYXRyaXh9IFxjb2xvcntSZWR9e3hfMX0gXFwgXGNvbG9ye1JlZH17eF8yfSBcXCBcY29sb3J7UmVkfXt4XzN9IFxlbmR7Ym1hdHJpeH0gICsgIFxiZWdpbntibWF0cml4fSBcY29sb3J7Qmx1ZX0ge2JfMV57WzFdfSB9IFxcIFxjb2xvcntCbHVlfSB7Yl8yXntbMV19IH0gXFwgXGNvbG9ye0JsdWV9IHtiXzNee1sxXX0gfSBcXCBcY29sb3J7Qmx1ZX0ge2JfNF57WzFdfSB9ICBcZW5ke2JtYXRyaXh9ID0gXGJlZ2lue2JtYXRyaXh9ICAgXGNvbG9ye09yYW5nZX0ge3dfMV57WzFdfSB9XlQgXGNvbG9ye1JlZH14ICArIFxjb2xvcntCbHVlfSB7Yl8xXntbMV19IH0gIFxcIFxjb2xvcntPcmFuZ2V9IHt3XzJee1sxXSB9IH0gXlQgXGNvbG9ye1JlZH14ICtcY29sb3J7Qmx1ZX0ge2JfMl57WzFdfSB9ICAgXFwgIFxjb2xvcntPcmFuZ2V9IHt3XzNee1sxXX0gfV5UIFxjb2xvcntSZWR9eCArXGNvbG9ye0JsdWV9IHtiXzNee1sxXX0gfSAgICBcXCAgXGNvbG9ye09yYW5nZX0ge3dfNF57WzFdfSB9XlQgXGNvbG9ye1JlZH14ICsgXGNvbG9ye0JsdWV9IHtiXzRee1sxXX0gfSAgICBcZW5ke2JtYXRyaXh9ICA9IFxiZWdpbntibWF0cml4fSBcY29sb3J7R3JlZW59IHt6XzFee1sxXX0gfSBcXCBcY29sb3J7R3JlZW59IHt6XzJee1sxXX0gfSBcXCBcY29sb3J7R3JlZW59IHt6XzNee1sxXX0gfSBcXCBcY29sb3J7R3JlZW59IHt6XzRee1sxXX0gfSBcZW5ke2JtYXRyaXh9IFxlbmR7ZXF1YXRpb259IAoKYW5kIGJ5ICBkZWZpbmluZyAgCiQkXGNvbG9ye09yYW5nZX17V157WzFdfX0gPSBcYmVnaW57Ym1hdHJpeH0gIFxjb2xvcntPcmFuZ2V9LSAmIFxjb2xvcntPcmFuZ2V9IHt3XzFee1sxXX0gfV5UICAmIFxjb2xvcntPcmFuZ2V9LVxcIFxjb2xvcntPcmFuZ2V9LSAgJiBcY29sb3J7T3JhbmdlfSB7d18yXntbMV0gfSB9IF5UICYgXGNvbG9ye09yYW5nZX0tIFxcIFxjb2xvcntPcmFuZ2V9LSAmIFxjb2xvcntPcmFuZ2V9IHt3XzNee1sxXX0gfV5UICAgICYgXGNvbG9ye09yYW5nZX0tIFxcIFxjb2xvcntPcmFuZ2V9LSAmIFxjb2xvcntPcmFuZ2V9IHt3XzRee1sxXX0gfV5UICAmIFxjb2xvcntPcmFuZ2V9LSBcZW5ke2JtYXRyaXh9IFxoc3BhY2V7MmNtfSBcY29sb3J7Qmx1ZX0ge2Jee1sxXX19ID0gIFxiZWdpbntibWF0cml4fSBcY29sb3J7Qmx1ZX0ge2JfMV57WzFdfSB9IFxcIFxjb2xvcntCbHVlfSB7Yl8yXntbMV19IH0gXFwgXGNvbG9ye0JsdWV9IHtiXzNee1sxXX0gfSBcXCBcY29sb3J7Qmx1ZX0ge2JfNF57WzFdfSB9ICBcZW5ke2JtYXRyaXh9IFxoc3BhY2V7MmNtfSBcY29sb3J7R3JlZW59IHt6XntbMV19IH0gID0gXGJlZ2lue2JtYXRyaXh9IFxjb2xvcntHcmVlbn0ge3pfMV57WzFdfSB9IFxcIFxjb2xvcntHcmVlbn0ge3pfMl57WzFdfSB9IFxcIFxjb2xvcntHcmVlbn0ge3pfM157WzFdfSB9IFxcIFxjb2xvcntHcmVlbn0ge3pfNF57WzFdfSB9ICBcZW5ke2JtYXRyaXh9IFxoc3BhY2V7MmNtfSBcY29sb3J7UHVycGxlfSB7YV57WzFdfSB9ICA9IFxiZWdpbntibWF0cml4fSBcY29sb3J7UHVycGxlfSB7YV8xXntbMV19IH0gXFwgXGNvbG9ye1B1cnBsZX0ge2FfMl57WzFdfSB9IFxcIFxjb2xvcntQdXJwbGV9IHthXzNee1sxXX0gfSBcXCBcY29sb3J7UHVycGxlfSB7YV80XntbMV19IH0gIFxlbmR7Ym1hdHJpeH0kJAp3ZSBjYW4gd3JpdGUKJCRcY29sb3J7R3JlZW59e3pee1sxXX0gfSA9IFdee1sxXX0geCArIGIgXntbMV19JCQKYW5kIHRoZW4gYnkgYXBwbHlpbmcgRWxlbWVudC13aXNlIEluZGVwZW5kZW50IGFjdGl2YXRpb24gZnVuY3Rpb24gJFxzaWdtYShcY2RvdCkkIHRvIHRoZSB2ZWN0b3IgJHpee1sxXX0kIChtZWFuaW5nIHRoYXQgJFxzaWdtYShcY2RvdCkkIGFyZSBhcHBsaWVkIGluZGVwZW5kZW50bHkgdG8gZWFjaCBlbGVtZW50IG9mIHRoZSBpbnB1dCB2ZWN0b3IgJHpee1sxXX0kKSB3ZSBnZXQ6CgokJFxjb2xvcntQdXJwbGV9e2Fee1sxXX19ID0gXHNpZ21hIChcY29sb3J7R3JlZW59eyB6XntbMV19IH0pLiQkClRoZSBvdXRwdXQgbGF5ZXIgY2FuIGJlIGNvbXB1dGVkIGluIHRoZSBzaW1pbGFyIHdheTogCgokJFxjb2xvcntZZWxsb3dHcmVlbn17el57WzJdfSB9ID0gV157WzJdfSBhXntbMV19ICsgYiBee1syXX0kJCAKCndoZXJlIAoKJCRcY29sb3J7T3JhbmdlfXtXXntbMl19fSA9IFxiZWdpbntibWF0cml4fSAgIFxjb2xvcntPcmFuZ2V9IHt3X3sxLDF9XntbMl19IH0gIFxcIApcY29sb3J7T3JhbmdlfSB7d197MSwyfV57WzJdfSB9ICBcXCBcY29sb3J7T3JhbmdlfSB7d197MSwzfV57WzJdfSB9ICBcXCBcY29sb3J7T3JhbmdlfSB7d197MSw0fV57WzJdfSB9ICBcXCAKXGVuZHtibWF0cml4fSBcaHNwYWNlezJjbX0gXGNvbG9ye0JsdWV9IHtiXntbMl19fSA9ICBcYmVnaW57Ym1hdHJpeH0gXGNvbG9ye0JsdWV9IHtiXzFee1syXX0gfSBcXCBcY29sb3J7Qmx1ZX0ge2JfMl57WzJdfSB9IFxcIFxjb2xvcntCbHVlfSB7Yl8zXntbMl19IH0gXFwgXGNvbG9ye0JsdWV9IHtiXzRee1syXX0gfSAgXGVuZHtibWF0cml4fSAkJAoKYW5kIGZpbmFsbHk6CgokJFxjb2xvcntQaW5rfXthXntbMl19fSA9IFxzaWdtYSAoIFxjb2xvcntMaW1lR3JlZW59e3pee1syXX0gfSlcbG9uZ3JpZ2h0YXJyb3cgXGNvbG9ye3JlZH17XGhhdHt5fX0kJAoKCiMjIyBBIHNob3J0IHByYWN0aWNlCgotIFtCaW5hcnkgQ2xhc3NpZmljYXRpb24gdGFza10oUFJBQ1RJQ0UvSWxsdXN0cmF0aW9uLUJpbmFyeS1jbGFzc2lmaWNhdGlvbi10YXNrLmh0bWwpIHVzaW5nIFIKCi0gW1ByZWRpY3Rpb24gdGFza10oUFJBQ1RJQ0UvaWxsdXN0cmF0aW9uLVNoYWxsb3ctTk4uaHRtbCkgdXNpbmcgUiAKCi0gW0JpbmFyeSBDbGFzc2lmaWNhdGlvbiB0YXNrXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZHJpdmUvMUdEZG1WVGtfWV9TVUt2RHJWV1BmOENLai1NNnIwTnlHI3Njcm9sbFRvPU9MdXVjOVFyU3hGbikgaW4gUHl0aG9uIChnb29nbGUgY29sbGFiKQoKLSBbUHJlZGljdGlvbiB0YXNrXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZHJpdmUvMWdwcjhYbDVZNTEtZFlEUTFCQTZsTmNKOHlacTNHUHZyKSBpbiBQeXRob24gKGdvb2dsZSBjb2xsYWIpCgojIyMgVGhlIEZvcndhcmQgUGFzcyAKCldlIGNhbiBnZW5lcmFsaXplIHRoaXMgc2ltcGxlIHByZXZpb3VzIG5ldXJhbCBuZXR3b3JrIHRvIGEgKipNdWx0aS1sYXllciBmdWxseS1jb25uZWN0ZWQgbmV1cmFsIG5ldHdvcmtzKiogYnkgc2Fja2luZyBtb3JlIGxheWVycyBnZXQgYSBkZWVwZXIgZnVsbHktY29ubmVjdGVkIG5ldXJhbCBuZXR3b3JrIGRlZmluaW5nIGJ5IHRoZSBGb3J3YXJkIFBhc3MgZXF1YXRpb25zLgoKClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2Vxbjpmb3J3YXJkLXBhc3N9CiBcYmVnaW57YXJyYXl9e2x9CiBcdGV4dHtMYXllciAxfX5+CiBcbGVmdFx7IAogCVxiZWdpbnthcnJheX17cnJjbH0KCSAgICAgXHF1YWR+fiZ7el57WzFdfSB9ICY9Jldee1sxXX1cb3ZlcmJyYWNle3h9XntcdGV4dHtJbnB1dH19ICtiXntbMV19IFxcCiAgICAgICAgICAgICBccXVhZH5+JnthXntbMV19IH0gJj0mU157WzFdfSh6XntbMV19KSAgXFwKICAgICAgICAgICAgIFxlbmR7YXJyYXl9CiBccmlnaHQuXFxbMjBwdF0KIFx0ZXh0e0xheWVyIDJ9fn4KIFxsZWZ0XHsgCiAJXGJlZ2lue2FycmF5fXtycmNsfQoJICAgICBccXVhZH5+Jnt6XntbMl19IH0gJj0mV157WzJdfWFee1sxXX0gICtiXntbMl19IFxcCiAgICAgICAgICAgICBccXVhZH5+JnthXntbMl19IH0gJj0mU157WzJdfSh6XntbMl19KSAgXFwKICAgICAgICAgICAgIFxlbmR7YXJyYXl9CiBccmlnaHQuXFxbMjBwdF0KXHF1YWQgXHZkb3RzXFxbMjBwdF0KIFx0ZXh0e0xheWVyIH1Mfn4KIFxsZWZ0XHsgCiAJXGJlZ2lue2FycmF5fXtycmNsfQoJICAgICAme3pee1tMXX0gfSAmPSZXXntbTF19YV57W0wtMV19ICArYl57W0xdfSBcXAogICAgICAgICAgICAgXHVuZGVyYnJhY2V7XGhhdHt5fX1fe1x0ZXh0e291dHB1dH19IH49ICZ7YV57W0xdfSB9ICY9JlNee1tMXX0oel57W0xdfSkuICBcXAogICAgICAgICAgICAgXGVuZHthcnJheX0KIFxyaWdodC4KXGVuZHthcnJheX0KXGVuZHtlcXVhdGlvbn0KCjxkaXYgY2xhc3MgPSAib3JhbmdlIj4KLSBUaGUgY29tcHV0YXRpb25hbCBjb3N0IG9mIHN1Y2ggYSBmb3J3YXJkIHBhc3MgaXMgYXQgYW4gb3JkZXIgb2YgdGhlIHRvdGFsIG51bWJlciBvZiB3ZWlnaHRzLgotIEF0IHRoZSAkXGVsbCQtdGggbGF5ZXIsIHRoZSBjb3N0IHRvIGNvbXB1dGUgJHt6XntbXGVsbF19IH0gPSBXXntbXGVsbF19YV57W1xlbGwtMV19ICArIGJee1tcZWxsXX0kIGFuZCAke2Fee1tcZWxsXX0gfSA9U157W1xlbGxdfSh6XntbXGVsbF19KSQgYXJlIG9mIHRoZSBvcmRlciAkTl9cZWxsIFx0aW1lcyBOX3tcZWxsLTF9ICsgTl9cZWxsJCBhbmQgJE5fXGVsbCQsIHJlc3BlY3RpdmVseS4gCi0gVG90YWwgY29tcHV0YXRpb25hbCBjb3N0IGlzIG9mIHRoZSBvcmRlciAkXHN1bV97XGVsbD0xfV5MIE5fXGVsbCAoTl97XGVsbC0xfSArMikgXGFwcHJveCAgXHN1bV97XGVsbD0xfV5MIE5fXGVsbCBOX3tcZWxsLTF9JCwgd2hpY2ggaXMgdGhlIHRvdGFsIG51bWJlciBvZiB3ZWlnaHRzLgo8L2Rpdj4KCgojIyMgQW4gRXhhbXBsZSB3aXRoIENvbmNyZXRlIERpbWVuc2lvbnMKCgohW10oc2luZ2xlX2hpZGRlbi5wbmcpCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgotIENvbnNpZGVyIHRoZSBuZXR3b3JrIHdpdGggb25lLWhpZGRlbiBsYXllci4gCi0gJE5fMD1wID0gNCQsICROXzE9NSQsIGFuZCAkTl8yPXE9MSQuIAotIFRoZSBkaW1lbnNpb24gb2YgJFdee1sxXX0kIGlzICQ1IFx0aW1lcyA0JAotIFRoZSBkaW1lbnNpb24gb2YgJGJee1sxXX0kIGlzICQ1JCwgdGhlIGRpbWVuc2lvbiBvZiAkV157WzJdfSQgaXMgJDEgXHRpbWVzIDUkLCBhbmQgdGhlIGRpbWVuc2lvbiBvZiAkYl57WzJdfSQgaXMgJDEkLiAKClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxbjpvcGVuZWQtb3V0LWV4YW1wbGUtMX0KZl9cdGhldGEoeCk9XHVuZGVyYnJhY2V7U157WzJdfShcb3ZlcmJyYWNle1dee1syXX1cdW5kZXJicmFjZXtTXntbMV19KFxvdmVyYnJhY2V7V157WzFdfXgrYl57WzFdCn19Xnt6XntbMV19fQopfV97YV57WzFdfX0rYl57WzJdfX1ee3pee1syXX19KX1fe2Fee1syXX19LCAgClxlbmR7ZXF1YXRpb259CgotIG51bWJlciBvZiBwYXJhbWV0ZXJzIGluICRcdGhldGEkIGlzICQ1IFx0aW1lcyA0ICsgNSArIDFcdGltZXMgNSArIDEgPSAzMSQuIAoKPC9kaXY+CgpUaGUgZGVlcGVyIG5ldHdvcmsgYmVsb3c6CgohW10obXVsdGlwbGVfaGlkZGVuLnBuZykKCmlzIHJlcHJlc2VudGVkIGJ5IAoKClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxbjpvcGVuZWQtb3V0LWV4YW1wbGUtMn0KZl9cdGhldGEoeCk9U157WzRdfShXXntbNF19U157WzNdfShXXntbM119U157WzJdfShXXntbMl19U157WzFdfShXXntbMV19eCtiXntbMV19KStiXntbMl19KSArYl57WzNdfSkrYl57WzRdfSkKXGVuZHtlcXVhdGlvbn0KClRoZSBudW1iZXIgb2YgcGFyYW1ldGVycyBpcwoKXFsKXHVuZGVyYnJhY2V7NFx0aW1lczQgKyA0fV97XHRleHR7SGlkZGVuIGxheWVyIDF9fSArIFx1bmRlcmJyYWNlezNcdGltZXMgNCArIDN9X3tcdGV4dHtIaWRkZW4gbGF5ZXIgMn19KyBcdW5kZXJicmFjZXs1XHRpbWVzIDMgKyA1fV97XHRleHR7SGlkZGVuIGxheWVyIDN9fSAgKyBcdW5kZXJicmFjZXsxIFx0aW1lcyA1ICsxfV97XHRleHR7T3V0cHV0IGxheWVyfX0gID0gNjEuClxdCgoKCiMjIFRoZSBTY2FsYXIgQmFzZWQgVmlldyBvZiB0aGUgTW9kZWwKClRoZSAkaSR0aCBuZXVyb24gb2YgbGF5ZXIgJFxlbGwkLCB3aXRoICRpPTEsXGxkb3RzLE5fXGVsbCQsIGlzIHR5cGljYWxseSBjb21wb3NlZCBvZiBib3RoICR6XntbXGVsbF19X2kkIGFuZCAkYV57W1xlbGxdfV9pJC4gVGhlIGByIGNvbG9yaXplKCJ0cmFuc2l0aW9uIGZyb20gbGF5ZXIiLCJyZWQiKWAgJFxlbGwtMSQgdG8gbGF5ZXIgJFxlbGwkIHRha2VzIHRoZSBvdXRwdXQgb2YgbGF5ZXIgJFxlbGwtMSQsIGFuICROX3tcZWxsLTF9JCBkaW1lbnNpb25hbCB2ZWN0b3IsIGFuZCBvcGVyYXRlcyBvbiBpdCBhcyBmb2xsb3dzLCAKCgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpzY2FsYXItbm4tZnVsbH0KXHN1YnN0YWNre1x0ZXh0e0FmZmluZX0gXFwgXHRleHR7VHJhbnNmb3JtYXRpb259fTpcbGVmdFx7XGJlZ2lue2FycmF5fXtsbGx9CnpfezF9XntbXGVsbF19Jj0md197KDEpfV57W1xlbGxdXntcdG9wfX0gYV57W1xlbGwtMV19ICtiX3sxfV57W1xlbGxdfSBcXAp6X3syfV57W1xlbGxdfSY9JndfeygyKX1ee1tcZWxsXV57XHRvcH19IGFee1tcZWxsLTFdfSArYl97Mn1ee1tcZWxsXX0gXFwKJlx2ZG90cyZcXAp6X3tOX1xlbGx9XntbXGVsbF19Jj0md197KE5fXGVsbCl9XntbXGVsbF1ee1x0b3B9fSBhXntbXGVsbC0xXX0gK2Jfe05fXGVsbH1ee1tcZWxsXX0KXGVuZHthcnJheX1ccmlnaHQuICAgClxoc3BhY2V7MC4xY219IFxSaWdodGFycm93IFxoc3BhY2V7MC4xY219XHN1YnN0YWNre1x0ZXh0e0FjdGl2YXRpb24gfSBcXCBcdGV4dHtTdGVwfX06XGxlZnRce1xiZWdpbnthcnJheX17bGxsfQphX3sxfV57W1xlbGxdfSAmPSZcc2lnbWFcbGVmdCh6X3sxfV57W1xlbGxdfVxyaWdodCkgXFwKYV97Mn1ee1tcZWxsXX0gJj0mXHNpZ21hXGxlZnQoel97Mn1ee1tcZWxsXX1ccmlnaHQpIFxcCiZcdmRvdHMmXFwKYV97Tl9cZWxsfV57W1xlbGxdfSAmPSZcc2lnbWFcbGVmdCh6X3tOX1xlbGx9XntbXGVsbF19XHJpZ2h0KQpcZW5ke2FycmF5fVxyaWdodC4sICAgClxlbmR7ZXF1YXRpb259CndoZXJlLApcWwp7d197KGopfV57W1xlbGxdfX1eXHRvcCA9IFxsZWZ0W3dfe2osIDF9XntbXGVsbF19fiBcbGRvdHMgfiB3X3tqLCBOX3tcZWxsLTF9fV57W1xlbGxdfVxyaWdodF0sClxxcXVhZApcdGV4dHtmb3J9IApccXF1YWQgaj0xLFxsZG90cyxOX1xlbGwsClxdCmlzIHRoZSAkaiR0aCByb3cgb2YgdGhlIHdlaWdodCBtYXRyaXggJFdee1tcZWxsXX0kLCBhbmQgJGJee1tcZWxsXX1faiQgaXMgdGhlICRqJHRoIGVsZW1lbnQgb2YgdGhlIGJpYXMgdmVjdG9yICRiXntbXGVsbF19JC4gSGVuY2UgdGhlIHBhcmFtZXRlcnMgYXNzb2NpYXRlZCB3aXRoIG5ldXJvbiAkaiQgaW4gbGF5ZXIgJFxlbGwkLCBhcmUgJHt3X3soail9XntbXGVsbF19fV5cdG9wJCBhbmQgJGJee1tcZWxsXX1faiQuCgoKCiMjIyBIb3cgZG8gd2UgY291bnQgbGF5ZXJzIGluIGEgbmV1cmFsIG5ldHdvcms/CgpXaGVuIGNvdW50aW5nIGxheWVycyBpbiBhIG5ldXJhbCBuZXR3b3JrIHdlIGNvdW50IGhpZGRlbiBsYXllcnMgYXMgd2VsbCBhcyB0aGUgb3V0cHV0IGxheWVyLCBidXQgd2UgZG9u4oCZdCBjb3VudCBhbiBpbnB1dCBsYXllci4KCiFbXShpbWFnZXMvZGVlcF9OTGF5ZXIucG5nKQoKSXQgaXMgYSBmb3VyIGxheWVyIG5ldXJhbCBuZXR3b3JrIHdpdGggdGhyZWUgaGlkZGVuIGxheWVycy4gCgoKCiMjIEFjdGl2YXRpb24gRnVuY3Rpb24gQWx0ZXJuYXRpdmVzCgpMZXQgZmlyc3QgY3JlYXRlIGEgc2ltcGxlIHBsb3QgZnVuY3Rpb24gZm9yIGVhY2ggYWN0aXZhdGlvbiBmdW5jdGlvbiBhbmQgaXRzIGRlcml2YXRpdmUuCgojIyMgU2NhbGFyIEFjdGl2YXRpb25zIGFuZCB0aGVpciBEZXJpdmF0aXZlcwoKYGBge3J9CmxpYnJhcnkoZ2dwbG90MikKZiA8LSBmdW5jdGlvbih4KSB7eH0KcGxvdF9hY3RpdmF0aW9uX2Z1bmN0aW9uIDwtIGZ1bmN0aW9uKGYsIHRpdGxlLCByYW5nZSl7CiAgZ2dwbG90KGRhdGEuZnJhbWUoeD1yYW5nZSksIG1hcHBpbmc9YWVzKHg9eCkpICsgCiAgICBnZW9tX2hsaW5lKHlpbnRlcmNlcHQ9MCwgY29sb3I9J3JlZCcsIGFscGhhPTEvNCkgKwogICAgZ2VvbV92bGluZSh4aW50ZXJjZXB0PTAsIGNvbG9yPSdyZWQnLCBhbHBoYT0xLzQpICsKICAgIHN0YXRfZnVuY3Rpb24oZnVuPWYsIGNvbG91ciA9ICJkb2RnZXJibHVlMyIpICsKICAgIGdndGl0bGUodGl0bGUpICsKICAgIHNjYWxlX3hfY29udGludW91cyhuYW1lPSd4JykgKwogICAgc2NhbGVfeV9jb250aW51b3VzKG5hbWU9JycpICsKICAgIHRoZW1lKHBsb3QudGl0bGUgPSBlbGVtZW50X3RleHQoaGp1c3QgPSAwLjUpKQp9CmBgYAoKCgojIyMgU2lnbW9pZCBmdW5jdGlvbiAKCiQkXHNpZ21hKHopPWcgKHopPVxmcmFjezF9ezErZV57LXp9fSQkCgpJdHMgZGVyaXZhdGl2ZSA6CgokJFxmcmFje2R9e2R6fVxzaWdtYSh6KT1cc2lnbWEoeikoMS1cc2lnbWEoeikpJCQKYGBge3IsIGVjaG89VFJVRX0KZiA8LSBmdW5jdGlvbih4KXsxIC8gKDEgKyBleHAoLXgpKX0KZGYgPC0gZnVuY3Rpb24oeCl7Zih4KSooMS1mKHgpKX0KcGxvdGYgPC0gcGxvdF9hY3RpdmF0aW9uX2Z1bmN0aW9uKGYsICdTaWdtb2lkJywgYygtNCw0KSkKcGxvdGRmIDwtIHBsb3RfYWN0aXZhdGlvbl9mdW5jdGlvbihkZiwgJ0Rlcml2YXRpdmUnLCBjKC00LDQpKQpgYGAKCmBgYHtyLCByZXN1bHRzPSdtYXJrdXAnLCBmaWcud2lkdGggPSAxNCwgZmlnLmhlaWdodCA9IDcsZmlnLm1hcmdpbiA9IEZBTFNFLGZpZy5jYXA9IlNpZ21vaWQgYW5kIGRlcml2YXRpdmUgZnVuY3Rpb24iLCBlY2hvPVRSVUUsZmlnLmZ1bGx3aWR0aCA9IFRSVUV9CmxpYnJhcnkoZ2dwdWJyKQpnZ2FycmFuZ2UocGxvdGYscGxvdGRmLG5yb3c9MSxuY29sPTIpCmBgYAoKIyMjIFJlTFUgZnVuY3Rpb24KCkEgcmVjZW50IGludmVudGlvbiB3aGljaCBzdGFuZHMgZm9yIFJlY3RpZmllZCBMaW5lYXIgVW5pdHMuIAoKJCRSZUxVKHopPVxtYXh7KDAs8J2Rpyl9JCQKCkRlc3BpdGUgaXRzIG5hbWUgYW5kIGFwcGVhcmFuY2UsIGl04oCZcyBub3QgbGluZWFyIGFuZCBwcm92aWRlcyB0aGUgc2FtZSBiZW5lZml0cyBhcyBTaWdtb2lkIGJ1dCB3aXRoIGJldHRlciBwZXJmb3JtYW5jZS4KCgpJdHMgZGVyaXZhdGl2ZSA6CgokJFxmcmFje2R9e2R6fVJlTFUoeik9IFxCaWdnXHsgXGJlZ2lue21hdHJpeH0gMSBcZW5zcGFjZSBpZiBcZW5zcGFjZSB6ID4gMCBcXCAwIFxlbnNwYWNlIGlmIFxlbnNwYWNlIHo8MCBcXCB1bmRlZmluZWQgXGVuc3BhY2UgaWYgXGVuc3BhY2UgeiA9IDAgXGVuZHttYXRyaXh9JCQKYGBge3IsIGVjaG89VFJVRX0KcmVjX2x1X2Z1bmMgPC0gZnVuY3Rpb24oeCl7IGlmZWxzZSh4IDwgMCAsIDAsIHggKX0KZHJlY19sdV9mdW5jIDwtIGZ1bmN0aW9uKHgpeyBpZmVsc2UoeCA8IDAgLCAwLCAxKX0KcGxvdGYgPC0gcGxvdF9hY3RpdmF0aW9uX2Z1bmN0aW9uKHJlY19sdV9mdW5jLCAnUmVMVScsIGMoLTQsNCkpCnBsb3RkZiA8LSBwbG90X2FjdGl2YXRpb25fZnVuY3Rpb24oZHJlY19sdV9mdW5jLCAnRGVyaXZhdGl2ZScsIGMoLTQsNCkpCmBgYAoKYGBge3IsIHJlc3VsdHM9J21hcmt1cCcsIGZpZy53aWR0aCA9IDE0LCBmaWcuaGVpZ2h0ID0gNyxmaWcubWFyZ2luID0gRkFMU0UsZmlnLmNhcD0iUmVMVSBhbmQgZGVyaXZhdGl2ZSBmdW5jdGlvbiIsIGVjaG89VFJVRSxmaWcuZnVsbHdpZHRoID0gVFJVRX0KZ2dhcnJhbmdlKHBsb3RmLHBsb3RkZixucm93PTEsbmNvbD0yKQpgYGAKCgoKCiMjIyBMZWFreVJlbHUgZnVuY3Rpb24gCgpMZWFreSBSZWx1IGlzIGEgdmFyaWFudCBvZiBSZUxVLiBJbnN0ZWFkIG9mIGJlaW5nIDAgd2hlbiAkejwwJCwgYSBsZWFreSBSZUxVIGFsbG93cyBhIHNtYWxsLCBub24temVybywgY29uc3RhbnQgZ3JhZGllbnQgJFxhbHBoYSQgKHVzdWFsbHksICRcYWxwaGE9MC4wMSQpLiBIb3dldmVyLCB0aGUgY29uc2lzdGVuY3kgb2YgdGhlIGJlbmVmaXQgYWNyb3NzIHRhc2tzIGlzIHByZXNlbnRseSB1bmNsZWFyLgoKCiQkTGVha2x5UmVMVSh6KT1cbWF4eyhcYWxwaGEgeizwnZGnKX0kJAoKCgpJdHMgZGVyaXZhdGl2ZSA6CgokJFxmcmFje2R9e2R6fUxlYWtseVJlTFUoeik9ICAgXGJlZ2lue2Nhc2VzfVxhbHBoYSAmIGlmIFwgXCB6PCAwIFxcMSAmIGlmIFwgXCB6XGdlcTBcXFxlbmR7Y2FzZXN9JCQKYGBge3IsIGVjaG89VFJVRX0KcmVjX2x1X2Z1bmMgPC0gZnVuY3Rpb24oeCl7IGlmZWxzZSh4IDwgMCAsIDAuMDEqeCwgeCApfQpkcmVjX2x1X2Z1bmMgPC0gZnVuY3Rpb24oeCl7IGlmZWxzZSh4IDwgMCAsIDAuMDEsIDEpfQpwbG90ZiA8LSBwbG90X2FjdGl2YXRpb25fZnVuY3Rpb24ocmVjX2x1X2Z1bmMsICdMZWFrbHlSZUxVJywgYygtNCw0KSkKcGxvdGRmIDwtIHBsb3RfYWN0aXZhdGlvbl9mdW5jdGlvbihkcmVjX2x1X2Z1bmMsICdEZXJpdmF0aXZlJywgYygtNCw0KSkKYGBgCgpgYGB7ciwgcmVzdWx0cz0nbWFya3VwJywgZmlnLndpZHRoID0gMTQsIGZpZy5oZWlnaHQgPSA3LGZpZy5tYXJnaW4gPSBGQUxTRSxmaWcuY2FwPSJMZWFrbHlSZUxVIGFuZCBkZXJpdmF0aXZlIGZ1bmN0aW9uIiwgZWNobz1UUlVFLGZpZy5mdWxsd2lkdGggPSBUUlVFfQpnZ2FycmFuZ2UocGxvdGYscGxvdGRmLG5yb3c9MSxuY29sPTIpCmBgYAoKCiMjIyBUYW5oIGZ1bmN0aW9uIAoKVGFuaCBzcXVhc2hlcyBhIHJlYWwtdmFsdWVkIG51bWJlciB0byB0aGUgcmFuZ2UgJFstMSwgMV0kLiBJdOKAmXMgbm9uLWxpbmVhci4gQnV0IHVubGlrZSBTaWdtb2lkLCBpdHMgb3V0cHV0IGlzIHplcm8tY2VudGVyZWQuIFRoZXJlZm9yZSwgaW4gcHJhY3RpY2UgdGhlICoqdGFuaCoqIG5vbi1saW5lYXJpdHkgaXMgYWx3YXlzIHByZWZlcnJlZCB0byB0aGUgc2lnbW9pZCBub25saW5lYXJpdHkuCgokJHRhbmgoeikgPVxmcmFje2Vee3p9LWVeey16fX17ZV57en0rZV57LXp9fSQkCgpJdHMgZGVyaXZhdGl2ZSA6CgokJFxmcmFje2R9e2R6fXRhbmgoeik9MS10YW5oKHopXjIkJAoKCmBgYHtyLCBlY2hvPVRSVUV9CnRhbmhfZnVuYyA8LSBmdW5jdGlvbih4KXt0YW5oKHgpfQpkdGFuaF9mdW5jIDwtIGZ1bmN0aW9uKHgpezEtKHRhbmgoeCkpKioyfQpwbG90ZiA8LSBwbG90X2FjdGl2YXRpb25fZnVuY3Rpb24odGFuaF9mdW5jLCAnVGFuSCcsIGMoLTQsNCkpCnBsb3RkZiA8LSBwbG90X2FjdGl2YXRpb25fZnVuY3Rpb24oZHRhbmhfZnVuYywgJ0Rlcml2YXRpdmUnLCBjKC00LDQpKQpgYGAKCgoKYGBge3IsIHJlc3VsdHM9J21hcmt1cCcsIGZpZy53aWR0aCA9IDE0LCBmaWcuaGVpZ2h0ID0gNyxmaWcubWFyZ2luID0gRkFMU0UsZmlnLmNhcD0iVGFuaCBhbmQgZGVyaXZhdGl2ZSBmdW5jdGlvbiIsIGVjaG89VFJVRSxmaWcuZnVsbHdpZHRoID0gVFJVRX0KZ2dhcnJhbmdlKHBsb3RmLHBsb3RkZixucm93PTEsbmNvbD0yKQpgYGAKCiMjIyBBIHJlY2FwCgoKIVtdKGFjdGl2YXRpb25fZnVuY3Rpb24ucG5nKQoKCgojIyMgTm9uIFNjYWxhciBBY3RpdmF0aW9ucyBhbmQgdGhlaXIgRGVyaXZhdGl2ZXMKCgotIFNvbWUgbGF5ZXJzIGFsc28gdXNlIG5vbi1zY2FsYXIgYWN0aXZhdGlvbiBmdW5jdGlvbnM6ICRTXntbXGVsbF19OiBcUmVee05fXGVsbH0gXHRvIFxSZV57Tl9cZWxsfSQgIGEgdmVjdG9yIHRvIHZlY3RvciBmdW5jdGlvbi4KCi1UaGUgbW9zdCBjb21tb24gZXhhbXBsZSBvZiB0aGlzIGlzIHRoZSAqKnNvZnRtYXggYWN0aXZhdGlvbioqIGZ1bmN0aW9uLCB0eXBpY2FsbHkgdXNlZCBmb3IgY2xhc3NpZmljYXRpb24gaW4gdGhlIGxhc3QgbGF5ZXIgJFxlbGwgPSBMJC4gCgotIFdlIG5vdyBkZW5vdGUgJE5fTCA9IEskIHNpbmNlIHRvIGRlYWwgd2l0aCBjbGFzc2lmaWNhdGlvbiBvZiAkSyQgY2xhc3NlcywKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXFuOmxhc3QtaXMtc29mdG1heH0KIGFee1tMXX0gPSBTX3tcdGV4dHtzb2Z0bWF4fX0oel57W0xdfSkuClxlbmR7ZXF1YXRpb259CgpUaGUgJFxSZV57S30gXHRvIFxSZV57S30kIHNvZnRtYXggYWN0aXZhdGlvbiBmdW5jdGlvbiBpcyBkZWZpbmVkIGFzLApcWwpTX3tcdGV4dHtzb2Z0bWF4fX0oeikgPSBcZnJhY3sxfXtcc3VtX3tpPTF9XntLfSBlXnt6X2l9fSAKXGJlZ2lue2JtYXRyaXh9CmVee3pfMX0gJiBcY2RvdHMgJiBlXnt6X3tLfX0KXGVuZHtibWF0cml4fV5cdG9wLApcXQoKCgoKIyBUaGUgRXhwcmVzc2l2ZSBQb3dlciBvZiBOZXVyYWwgTmV0d29ya3MKCi0gTmV1cmFsIG5ldHdvcmtzIGFyZSBrbm93biBmb3IgYmVpbmcgYWJsZSB0byAkXGNvbG9ye0JsdWV9e1x0ZXh0cm17YXBwcm94aW1hdGV9fSQgYXJiaXRyYXJpbHkgY29tcGxleCBmdW5jdGlvbnMuCgotICAkXGNvbG9ye1JlZH17XHRleHRybXtBIEdlbmVyYWwgQXBwcm94aW1hdGlvbiBSZXN1bHR9fSQ6Cgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgoKKipUaGVvcmVtKiouIENvbnNpZGVyIGEgY29udGludW91cyBmdW5jdGlvbiAkZl4qOiB7XGNhbCBLfSBcdG8gXFJlXnEkIHdoZXJlICR7XGNhbCBLfSBcc3Vic2V0IFxSZV5wJCBpcyBhIGNvbXBhY3Qgc2V0LiBUaGVuIGZvciBhbnkgYW55ICRcc2lnbWFee1sxXX0oXGNkb3QpJCBub24tcG9seW5vbWlhbCBhY3RpdmF0aW9uIGZ1bmN0aW9uIGFuZCBhbnkgJFx2YXJlcHNpbG9uID4gMCQgdGhlcmUgZXhpc3RzIGFuICROXzEkIGFuZCBwYXJhbWV0ZXJzICRXXntbMV19XGluXFJlXntOXzFcdGltZXMgcH0kLCAkYl57WzFdfSBcaW4gXFJlXntOXzF9JCwgYW5kICRXXntbMl19XGluIFxSZV57cVx0aW1lcyBOXzF9JCwgIHN1Y2ggdGhhdCB0aGUgZnVuY3Rpb24gClxbIAp7Zn1fXHRoZXRhKHgpPVdee1syXX1TXntbMV19KFdee1sxXX14K2Jee1sxXX0pLApcXQpzYXRpc2ZpZXMgJHx8e2Z9X1x0aGV0YSh4KS1mXiooeCl8fDxcdmFyZXBzaWxvbiQgZm9yIGFsbCAkeFxpbiB7XGNhbCBLfSQuCgo8L2Rpdj4KCi0gSGVuY2UgdGhpcyB0aGVvcmVtIHN0YXRlcyB0aGF0IGVzc2VudGlhbGx5IGByIGNvbG9yaXplKCJhbGwgZnVuY3Rpb25zICIsImJsdWUiKWAgY2FuIGJlIGFwcHJveGltYXRlZCB0byBhcmJpdHJhcnkgcHJlY2lzaW9uIGRpY3RhdGVkIHZpYSAkXHZhcmVwc2lsb24kLiAKCi0gUHJhY3RpY2FsbHkgZm9yIGNvbXBsZXggZnVuY3Rpb25zICRmXiooXGNkb3QpJCBhbmQgc21hbGwgJFx2YXJlcHNpbG9uJCBvbmUgbWF5ICoqbmVlZCBsYXJnZSAkTl8xJCoqLiAKLSBZZXQsIHRoZSB0aGVvcmVtIHN0YXRlcyB0aGF0IGl0IGlzIGFsd2F5cyBwb3NzaWJsZS4gCgotIFtMZXQgdHJ5IGl0IGluIGEgc2hvcnQgcHJhY3RpY2VdKFBSQUNUSUNFL0lsbHVzdHJhdGlvbl9BcHByb3hfQWJpbGl0eS5odG1sKSB1c2luZyBSLgoKLSBbTGV0IHRyeSBpdCBpbiBhIHNob3J0IHByYWN0aWNlXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZHJpdmUvMTJQRVRkcEIyQjBZMm1UR3pZVVE4WDZNMEd4Um80Y1FOI3Njcm9sbFRvPWtObUItSldCYVg1OSkgdXNpbmcgUHl0aG9uIChnb29nbGUgY29sbGFiKS4KCiMjIFRoZSBTdHJlbmd0aCBvZiBhIEhpZGRlbiBMYXllciBmb3IgQ2xhc3NpZmljYXRpb24KCi0gU2hhbGxvdyBuZXVyYWwgbmV0d29ya3Mgc3VjaCBhcyBgciBjb2xvcml6ZSgibG9naXN0aWMgcmVncmVzc2lvbiBvciBzb2Z0bWF4IHJlZ3Jlc3Npb24iLCJibHVlIilgIGNhbiBiZSB1c2VkIHRvIGNyZWF0ZSBjbGFzc2lmaWVycyB3aXRoIGByIGNvbG9yaXplKCJsaW5lYXIgZGVjaXNpb24iLCJibHVlIilgIGJvdW5kYXJpZXMuIAoKLSBGb3IgY2FzZXMgd2hlcmUgbW9yZSBnZW5lcmFsIGRlY2lzaW9uIGJvdW5kYXJpZXMgYXJlIG5lZWRlZCwgb25lIG1heSBhdHRlbXB0IHRvIGByIGNvbG9yaXplKCJjcmVhdGUgYWRkaXRpb25hbCB0cmFuc2Zvcm1lZCBmZWF0dXJlcyIsImJsdWUiKWAgd2hpbGUgc3RpbGwgdXNpbmcgdGhlc2UgYmFzaWMgbW9kZWxzLgoKLSBgciBjb2xvcml6ZSgiRXhwcmVzc2l2ZW5lc3Mgb2YgbW9kZWxzIiwicmVkIilgIHdpdGggYSBzaW5nbGUgaGlkZGVuIGxheWVyIChvciBtb3JlKSBjYW4geWllbGQgYSB2ZXJzYXRpbGUgYWx0ZXJuYXRpdmUgdG8gc2hhbGxvdyBuZXR3b3Jrcy4gCgoKIVtdKGV4cHJlc3NpdmVfZGVlcC5wbmcpCgoqKlRpbWUgdG8gYSBzaG9ydCBwcmFjdGljZSoqCgpbRXhwcmVzc2l2aXR5IGV4YW1wbGVdKFBSQUNUSUNFL0lsbHVzdHJhdGlvbi1Ob24tbGluZWFyLWJ1bmRhcnkuaHRtbCkKCgojIyBTdHlsaXplZCBGdW5jdGlvbnMgdmlhIFNpbXBsZSBNb2RlbHMKCi0gYHIgY29sb3JpemUoIkV4cHJlc3NpdmUgcG93ZXIgIiwicmVkIilgIG9mIGZlZWRmb3J3YXJkIG5ldXJhbCBuZXR3b3JrcyBieSBjb25zaWRlcmluZyBzcGVjaWZpYyBzdHlsaXplZCBmdW5jdGlvbnMKCi0gICoqbXVsdGlwbGljYXRpb24gb2YgdHdvIGlucHV0cyoqOiBBIHNpbXBsZSBjb25zdHJ1Y3Rpb24gb2YgYSBzaW5nbGUgaGlkZGVuIGxheWVyIG5ldHdvcmsgd2l0aCAkcD0yJCBhbmQgJHE9MSQsIGFsbG93cyB0byBjcmVhdGUgYSBmdW5jdGlvbiAkZl9cdGhldGEoXGNkb3QpJCwgcGFyYW1ldHJpemVkIGJ5ICRcbGFtYmRhID4wJCwgc3VjaCB0aGF0IGZvciBpbnB1dCAkeD0oeF8xLHhfMikkLCB0aGUgZnVuY3Rpb24gYXBwcm94aW1hdGVseSBpbXBsZW1lbnRzIG11bHRpcGxpY2F0aW9uIG9mIGlucHV0cywKXGJlZ2lue2VxdWF0aW9ufQpmX1x0aGV0YSh4XzEseF8yKSBcYXBwcm94IHhfMSB4XzIuClxlbmR7ZXF1YXRpb259CgotIEltcG9ydGFudGx5LCB0aGUgYXBwcm94aW1hdGlvbiBlcnJvciB2YW5pc2hlcyBhcyAkXGxhbWJkYSBcdG8gMCQKLSBgciBjb2xvcml6ZSgiUmVxdWlyZXMgb25seSAiLCJyZWQiKWAgICROXzEgPSA0JCBuZXVyb25zIGluIHRoZSBzaW5nbGUgaGlkZGVuIGxheWVyLiAKCgohW10obXVsdGlwbGljYXRpb25fZ2F0ZS5wbmcpCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgotIFRoZSBhY3RpdmF0aW9uIGZ1bmN0aW9uIG9mIHRoZSBvdXRwdXQgbGF5ZXIgaXMgdGhlIGlkZW50aXR5LiAKCi0gVGhlcmUgYXJlIG5vIGJpYXMgdGVybXMsIGFuZCB0aGUgd2VpZ2h0IG1hdHJpY2VzIGFyZSwKCgpcWwpXXntbMV19ID0KXGJlZ2lue2JtYXRyaXh9ClxsYW1iZGEgJiBcbGFtYmRhIFxcCi1cbGFtYmRhICYgLVxsYW1iZGEgXFwKXGxhbWJkYSAmIC1cbGFtYmRhIFxcCi1cbGFtYmRhICYgXGxhbWJkYSBcXApcZW5ke2JtYXRyaXh9LApccXF1YWQKXHRleHR7YW5kfQpccXF1YWQgCldee1syXX09ClxiZWdpbntibWF0cml4fQpcbXUgJiBcbXUgJiAtXG11ICYgLVxtdVxcClxlbmR7Ym1hdHJpeH0sICAKXF0gCgp3aXRoICRcbXU9XGJpZyh7NFxsYW1iZGFeMlxkZG90e1xzaWdtYX0oMCl9XGJpZyleey0xfSQuIAoKLSAkXGRkb3R7XHNpZ21hfSgwKSQgcmVwcmVzZW50cyB0aGUgc2Vjb25kIGRlcml2YXRpdmUgb2YgdGhlIHNjYWxhciBhY3RpdmF0aW9uIGZ1bmN0aW9uIG9mIHRoZSBoaWRkZW4gbGF5ZXIgKCRcZWxsID0gMSQpIGF0ICQwJC4gSGVuY2UgdGhlIG1vZGVsIGFzc3VtZXMgJFxzaWdtYV57WzFdfShcY2RvdCkkIGlzIHR3aWNlIGRpZmZlcmVudGlhYmxlIChhdCAkMCQpIHdpdGggYSBub24temVybyBzZWNvbmQgZGVyaXZhdGl2ZSBhdCB6ZXJvLgoKCi0gSXQgdHVybnMgb3V0IHRoYXQgCgpcWwpmX1x0aGV0YSh4XzEsIHhfMikgPSBcZnJhY3tcc2lnbWFcYmlnKFxsYW1iZGEoeF8xK3hfMilcYmlnKStcc2lnbWFcYmlnKFxsYW1iZGEoLXhfMS14XzIpXGJpZyktXHNpZ21hXGJpZyhcbGFtYmRhKHhfMS14XzIpXGJpZyktXHNpZ21hXGJpZyhcbGFtYmRhKC14XzEreF8yKVxiaWcpfXs0IFxsYW1iZGFeMlxkZG90e1xzaWdtYX0oMCl9LCAKXF0KCldlIG1heSBub3cgdXNlIGEgVGF5bG9yIGV4cGFuc2lvbiAgb2YgJFxzaWdtYShcY2RvdCkkIGFyb3VuZCB0aGUgb3JpZ2luLAoKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6dGF5bG9yLXNpZ21hfQpcc2lnbWEodSk9XHNpZ21hKDApK1xkb3R7XHNpZ21hfSgwKSB1ICsgXGRkb3R7XHNpZ21hfSgwKSBcZnJhY3t1XnsyfX17Mn0re099XGxlZnQodV57M31ccmlnaHQpLApcZW5ke2VxdWF0aW9ufQoKd2l0aCAkTyhoXmspJCBkZW5vdGluZyBhIGZ1bmN0aW9uIHN1Y2ggdGhhdCAkTyhoXmspL2heayQgZ29lcyB0byBhIGNvbnN0YW50IGFzICRoIFx0byAwJC4gCgo8L2Rpdj4KCi0gVGh1cyB0aGUgYXBwcm94aW1hdGlvbiBtdWx0aXBsaWNhdGlvbiBvZiB0d28gaW5wdXQgZmVhdHVyZXMgICR4XzEkIGFuZCAkeF8yJCAobm90ZWQgJGZfXHRoZXRhKHhfMSwgeF8yKSQpIGlzIGdpdmVuIGJ5CgoKXFsKZl9cdGhldGEoeF8xLCB4XzIpIAogXGVxdWl2IFxmcmFje1xzaWdtYVxiaWcoXGxhbWJkYSh4XzEreF8yKVxiaWcpK1xzaWdtYVxiaWcoXGxhbWJkYSgteF8xLXhfMilcYmlnKS1cc2lnbWFcYmlnKFxsYW1iZGEoeF8xLXhfMilcYmlnKS1cc2lnbWFcYmlnKFxsYW1iZGEoLXhfMSt4XzIpXGJpZyl9ezQgXGxhbWJkYV4yXGRkb3R7XHNpZ21hfSgwKX09eF8xIHhfMlxsZWZ0KDEre099XGxlZnQoXGxhbWJkYSAoIHhfMV57Mn0reF8yXnsyfSlccmlnaHQpXHJpZ2h0KS4KXF0KCgoKSGVuY2UgYXMgJFxsYW1iZGEgXHRvIDAkIHRoZSBkZXNpcmVkIGdvYWwgYmVjb21lcyBleGFjdC4gCgojIyBGZWF0dXJlIEZvY3VzIHdpdGggTmV1cmFsIE5ldHdvcmtzCgoKKipGZWF0dXJlIGVuZ2luZWVyaW5nKio6ICBhZGRpdGlvbmFsIGZlYXR1cmVzIGJ5IHRyYW5zZm9ybWluZyBleGlzdGluZyBmZWF0dXJlcy4gCgoqKkV4YW1wbGU6KiogIGNvbnNpZGVyICAke3B9JCBmZWF0dXJlcyAke3h9XzEsXGxkb3RzLHt4fV97e3B9fSQuCgotIGByIGNvbG9yaXplKCJ3ZSB3aXNoIHRvIGNvbnN0cnVjdCAiLCJibHVlIilgICRcdGlsZGV7cH0gPSBwKHArMSkvMiQgZmVhdHVyZXMgYmFzZWQgb24gYHIgY29sb3JpemUoImFsbCBwb3NzaWJsZSBwYWlyd2lzZSBpbnRlcmFjdGlvbnMgIiwicmVkIilgICAke3h9X2kge3h9X2okIGZvciAkaSxqID0xLFxsZG90cyx7cH0kLgoKLSBGb3IgaW5zdGFuY2UgaWYgJHtwfSA9IDEsMDAwJCB0aGVuIHdlIGFycml2ZSBhdCAkXHRpbGRle3B9IFxhcHByb3ggNTAwLDAwMCQuIAoKLSBgciBjb2xvcml6ZSgiTGluZWFyIG1vZGVsICIsImJsdWUiKWAgIGFjdGluZyBvbiB0aGUgdHJhbnNmb3JtZWQgZmVhdHVyZXMgJFx0aWxkZXt4fSQgb3IgYSBgciBjb2xvcml6ZSgic2luZ2xlIGhpZGRlbiBsYXllciAiLCJyZWQiKWAgbmV1cmFsIG5ldHdvcmsgbW9kZWwgYWN0aW5nIG9uIHRoZSBvcmlnaW5hbCBmZWF0dXJlcyAkeCQgPy4gCgotIGByIGNvbG9yaXplKCJMaW5lYXIgbW9kZWwgIiwiYmx1ZSIpYCB3ZSBoYXZlICRcdGlsZGV7Zn1fXHRoZXRhKFx0aWxkZXt4fSkgPSBcdGlsZGV7d31eXHRvcCBcdGlsZGV7eH0kIHdoZXJlIHRoZSBsZWFybmVkIHdlaWdodCB2ZWN0b3IgJFx0aWxkZXt3fSQsIGhhcyAkXHRpbGRle3B9JCBwYXJhbWV0ZXJzLiAKCi0gYHIgY29sb3JpemUoIlNpbmdsZSBoaWRkZW4gbGF5ZXIgTk4iLCJyZWQiKWAsIHRoZXJlIGFyZSAkcCQgaW5wdXRzLCAkcT0xJCBvdXRwdXQsIGFuZCAkTl8xJCB1bml0cyBpbiB0aGUgaGlkZGVuIGxheWVyLiBUaHVzIHRoZSBudW1iZXIgb2YgcGFyYW1ldGVyczogICROXzEgXHRpbWVzIHtwfSArIE5fMSArIE5fMSArIDEkCgoKLSBgciBjb2xvcml6ZSgiTm90IGFsbCBpbnRlcmFjdGlvbnMiLCJyZWQiKWAgIChwcm9kdWN0IGZlYXR1cmVzKSBhcmUgKipyZWxldmFudCoqLgoKLSBMZXQgY29uc2lkZXIgb25seSBhICoqZnJhY3Rpb24gJFxhbHBoYSQqKiBvZiB0aGUgaW50ZXJhY3Rpb25zIGFyZSByZWxldmFudC4gCgotIGByIGNvbG9yaXplKCJUaGUgbXVsdGlwbGljYXRpb24gZXhhbXBsZSIsImJsdWUiKWAgcmVxdWlyZXMgKio0IGhpZGRlbiB1bml0cyoqICB0byBgciBjb2xvcml6ZSgiYXBwcm94aW1hdGUiLCJibHVlIilgIGFuIGludGVyYWN0aW9uLgoKLSBUaGlzIGV4YW1wbGUgaGludHMgYXQgdGhlIGZhY3QgdGhhdCB3aXRoICoqJE5fMSBcYXBwcm94IDQgXCwgXGFscGhhIFwsIHAkIGhpZGRlbiB1bml0cyoqIHdlIG1heSBiZSBhYmxlIHRvIGNhcHR1cmUgdGhlICoqa2V5KiogaW50ZXJhY3Rpb25zLiAKCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgotIFRoZSBiYXNpYyBsaW5lYXIgbW9kZWwgc3RpbGwgcmVxdWlyZXMgdGhlIGZ1bGwgc2V0IG9mIHBhcmFtZXRlcnMuIFdpdGggdGhpcywgY29tcGFyZSB0aGUgbnVtYmVyIG9mIHBhcmFtZXRlcnMsCgpcWwpcdW5kZXJicmFjZXtcZnJhY3sxfXsyfXAocCsxKX1fe1x0ZXh0e0xpbmVhciBNb2RlbH19ClxxcXVhZApcdGV4dHt2cy59ClxxcXVhZApcdW5kZXJicmFjZXs0IFxhbHBoYSBwKHArMikrMX1fe1x0ZXh0e05ldXJhbCBOZXR3b3JrfX0uClxdCgotIE9ic2VydmUgdGhhdCAkcF4yJCBpcyB0aGUgZG9taW5hbnQgdGVybSBpbiBib3RoIG1vZGVscyBidXQgZm9yICRcYWxwaGEgPCAxLzgkIGFuZCBsYXJnZSAkcCQsIHRoZSBuZXVyYWwgbmV0d29yayBtb2RlbCBoYXMgbGVzcyBwYXJhbWV0ZXJzLiAKCi0gV2l0aCAkcD0xLDAwMCQgaWYgJFxhbHBoYSA9IDAuMDIkICgkMjAkIHNpZ25pZmljYW50IGludGVyYWN0aW9ucykgdGhlbiB0aGUgYHIgY29sb3JpemUoImxpbmVhciBtb2RlbCBoYXMgYW4gb3JkZXIgb2YgJDUwMCwwMDAkIHBhcmFtZXRlcnMiLCJibHVlIilgIHdoaWxlIGByIGNvbG9yaXplKCJ0aGUgbmV1cmFsIG5ldHdvcmsgb25seSBoYXMgb3JkZXIgb2YgJDgwLDAwMCQgcGFyYW1ldGVycyIsInJlZCIpYC4gCjwvZGl2PgoKCiMjIEhpZ2hlciBNb2RlbCBDb21wbGV4aXR5IGJ5IGFuIEluY3JlYXNlIG9mIERlcHRoCgoKLSBUbyBnYWluICoqaGlnaCBleHByZXNzaXZlIHBvd2VyKiosIHRoaXMgbW9kZWwgbWlnaHQgcmVxdWlyZSBhIHZlcnkgbGFyZ2UgbnVtYmVyIG9mIHVuaXRzICgkTl8xJCBuZWVkcyB0byBiZSB2ZXJ5IGxhcmdlKS4gSGVuY2UgZ2FpbmluZyBzaWduaWZpY2FudCBleHByZXNzaXZlIHBvd2VyIG1heSByZXF1aXJlIGEgKip2ZXJ5IGxhcmdlIG51bWJlciBvZiBwYXJhbWV0ZXJzKiouIAoKLSBgciBjb2xvcml6ZSgiVGhlIHBvd2VyIG9mIGRlZXAgbGVhcm5pbmciLCJibHVlIilgIHRoZW4gYXJpc2VzIHZpYSAqKnJlcGVhdGVkIGNvbXBvc2l0aW9uIG9mIG5vbi1saW5lYXIgYWN0aXZhdGlvbnMqKiBmdW5jdGlvbnMgdmlhIGFuIGluY3JlYXNlIG9mIGRlcHRoIChhbiBpbmNyZWFzZSBvZiAkTCQpLiAKCi0gTm90ZSBmaXJzdCB0aGF0IGByIGNvbG9yaXplKCJpZiB0aGUgaWRlbnRpdHkgYWN0aXZhdGlvbiBmdW5jdGlvbiIsImJsdWUiKWAgaXMgdXNlZCBpbiBlYWNoIGhpZGRlbiBsYXllciwgdGhlbiB0aGUgbmV0d29yayByZWR1Y2VzIHRvIGEgc2hhbGxvdyBuZXVyYWwgbmV0d29yaywKXFsKZl9cdGhldGEoeCk9U157W0xdfShcdGlsZGV7V30geCArIFx0aWxkZXtifSksClxdCgpcWwpcdGlsZGV7V30gPSBXXntbTF19V157W0wtMV19XGNkb3QgXGxkb3RzIFxjZG90IFdee1sxXX0sClxxcXVhZApcdGV4dHthbmR9ClxxcXVhZApcdGlsZGV7Yn0gPSBcc3VtX3tcZWxsID0gMX1eTCBcQmlnKFxwcm9kX3tcdGlsZGV7XGVsbH0gPSBcZWxsICsgMX1eTCBXXntbXHRpbGRle1xlbGx9XX1cQmlnKSBiXntbXGVsbF19LgpcXQoKLSBUaGUgbW9kZWwgcmVkdWNlcyB0byBiZSBhIGxpbmVhciAoYWZmaW5lKSBtb2RlbC4gVGh1cywgd2UgaGF2ZSAgKipubyBnYWluIGJ5IGdvaW5nIGRlZXBlcioqIGFuZCBhZGRpbmcgbXVsdGlwbGUgbGF5ZXJzIHdpdGggaWRlbnRpdHkgYWN0aXZhdGlvbnMuIAoKLSBUaGUgZXhwcmVzc2l2aXR5IG9mIHRoZSBuZXVyYWwgbmV0d29yayBjb21lcyBmcm9tIGByIGNvbG9yaXplKCJ0aGUgY29tcG9zaXRpb24gb2Ygbm9uLWxpbmVhciBhY3RpdmF0aW9uIGZ1bmN0aW9ucyIsInJlZCIpYC4gCgotIFRoZSAqKnJlcGVhdGVkIGNvbXBvc2l0aW9ucyoqIG9mIHN1Y2ggZnVuY3Rpb25zIGNhbiAqKnJlZHVjZSoqIHRoZSBudW1iZXIgb2YgdW5pdHMgbmVlZGVkIGluIGVhY2ggbGF5ZXIgaW4gY29tcGFyaXNvbiB0byBhIG5ldHdvcmsgd2l0aCBhIHNpbmdsZSBoaWRkZW4gbGF5ZXIuIEEgY29uc2VxdWVuY2UgaXMgdGhhdCB0aGUgcGFyYW1ldGVyIHNwYWNlIGByIGNvbG9yaXplKCJpcyByZWR1Y2VkIGFzIHdlbGwiLCJyZWQiKWAuCgojIyMjIEV4YW1wbGUKCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgotIFdlIHJldmlzaXQgdGhlIHByZXZpb3VzIGV4YW1wbGUgaW52b2x2aW5nIG1vZGVscyB1c2luZyBpbnRlcmFjdGlvbnMgb2Ygb3JkZXIgMiAoaS5lLiAkeF9peF9qJCkuIExldCB1cyBjb25zaWRlciBhIGhpZ2hlciBjb21wbGV4aXR5IG1vZGVsIGJ5IGV4cGxvaXRpbmcgcG90ZW50aWFsIGhpZ2gtb3JkZXIgaW50ZXJhY3Rpb25zLCBuYW1lbHkgYHIgY29sb3JpemUoInByb2R1Y3RzIG9mIHIgaW5wdXRzIiwicmVkIilgLiAgCgotIENvbnNpZGVyIGEgZnVsbHkgY29ubmVjdGVkIGZlZWRmb3J3YXJkIG5ldHdvcmsgd2l0aCAkcCQgaW5wdXRzLCAkcT0xJCBvdXRwdXQsIGFuZCAkTCBcZ2UgMiQgbGF5ZXJzIHdpdGggc2FtZSBzaXplICgkTl57W1xlbGxdfT1OJCBpbiBlYWNoIGxheWVyKS4gIAoKLSBXaXRoICROIFxhcHByb3ggNCBcLCBcYWxwaGEgXCwgcCQgaGlkZGVuIHVuaXRzIHdlIG1heSBiZSBhYmxlIGByIGNvbG9yaXplKCJ0byBjYXB0dXJlIiwicmVkIilgIHRoZSAkXGFscGhhIHAkIHJlbGV2YW50IGludGVyYWN0aW9ucyBvZiBvcmRlciAkcj0yJC4gCgotIGByIGNvbG9yaXplKCJUaGVuIGJ5IG1vdmluZyBmb3J3YXJkIGluIHRoZSBuZXR3b3JrIiwiYmx1ZSIpYCwgdGhlIHN1YnNlcXVlbnQgYWRkaXRpb24gb2YgYSBsYXllciB3aXRoICROIFxhcHByb3ggNCBcLCBcYWxwaGEgXCwgcCQgaGlkZGVuIHVuaXRzIHdpbGwgY2FwdHVyZSAgaW50ZXJhY3Rpb25zIG9mIG9yZGVyICRyPTJeMiQgYW5kIHNvIG9uLCB1bnRpbCB3ZSBjYXB0dXJlIGludGVyYWN0aW9uIG9mICBvcmRlciAgJHI9Ml57TH0kIGF0IHRoZSBvdXRwdXQgbGF5ZXIuCgotIEhlbmNlIHRvIGFjaGlldmUgaW50ZXJhY3Rpb25zIG9mIG9yZGVyICRyJCB3ZSBtYXkgcmVxdWlyZSAkTCBcYXBwcm94IFxsb2dfMiByJCBvciAkTCA9IFxsY2VpbCBcbG9nXzIgciBccmNlaWwkLiAKClRoZSBudW1iZXIgb2YgcGFyYW1ldGVycyBpcywgCgpcWwpcdW5kZXJicmFjZXtOIFx0aW1lcyB7cH0gK059X3tcdGV4dHtGaXJzdCBoaWRkZW4gbGF5ZXJ9fSsgXHVuZGVyYnJhY2V7KEwtMilcdGltZXMoTl4yICsgTil9X3tcdGV4dHtJbm5lciBoaWRkZW4gbGF5ZXJzfX0gKyBcdW5kZXJicmFjZXtOKzF9X3tcdGV4dHtPdXRwdXQgbGF5ZXJ9fSBcYXBwcm94IEwgTl4yLgpcXQoKLSBgciBjb2xvcml6ZSgiRm9yIGV4YW1wbGUiLCJyZWQiKWAsIGFzc3VtZSB3ZSB3aXNoIHRvIGhhdmUgYSBtb2RlbCBmb3IgJHA9MSwwMDAkIGZlYXR1cmVzIHRoYXQgc3VwcG9ydHMgYWJvdXQgJDIwJCBtZWFuaW5nZnVsIGludGVyYWN0aW9ucyBvZiBvcmRlciAkcj01MDAkLiAKCi0gSGVuY2Ugd2UgY2FuIGNvbnNpZGVyICRcYWxwaGEgPSAwLjAyJC4gYHIgY29sb3JpemUoIldpdGggYSBtb2RlbCBub3QgaW52b2x2aW5nIGhpZGRlbiBsYXllcnMiLCJyZWQiKWAsIHdlIGNhbm5vdCBzcGVjaWFsaXplIGZvciBhbiBvcmRlciBvZiAkMjAkIGludGVyYWN0aW9ucyBhbmQgdGh1cyB3ZSByZXF1aXJlIGEgZnVsbCBtb2RlbCBvZiBvcmRlciAkcF5yL3IhIFxhcHByb3ggMTBeezM2NX0kIHBhcmFtZXRlcnMuIAoKPC9kaXY+CgohW10oZGVlcF9OTi5wbmcpCgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgoKLSBgciBjb2xvcml6ZSgiV2l0aCBzIGRlZXAgbW9kZWwiLCJyZWQiKWAgd2UgcmVxdWlyZSBhYm91dCAkTD0xMCBcYXBwcm94IFxsb2dfMiA1MDAkIGxheWVycyBhbmQgJE4gPSA0IFxhbHBoYSBwID0gODAkIG5ldXJvbnMgcGVyIGxheWVyLCBhbmQgdGh1cyB0aGUgdG90YWwgbnVtYmVyIG9mIHBhcmFtZXRlcnMgaXMgYXQgdGhlIG9yZGVyIG9mICRMTl4yID0gIDU3LDYwMCQuIAoKYHIgY29sb3JpemUoIlRoaXMgZGVlcCBjb25zdHJ1Y3Rpb24gd2hpY2ggY2FuIGNhcHR1cmUgYSBkZXNpcmVkIHNldCBvZiBtZWFuaW5nZnVsIGludGVyYWN0aW9ucyBpcyBjbGVhcmx5IG1vcmUgZmVhc2libGUgYW5kIGVmZmljaWVudCB0aGFuIHRoZSBzaGFsbG93IGNvbnN0cnVjdGlvbiBvZiBhc3Ryb25vbWljYWwgc2l6ZSIsInJlZCIpYCAKCjwvZGl2PgoKCgojIFRoZSBCYWNrIFByb3BhZ2F0aW9uIEFsZ29yaXRobQoKLSBBICoqa2V5IGFsZ29yaXRobSoqIGZvciBsZWFybmluZyBwYXJhbWV0ZXJzIGluIGRlZXAgbGVhcm5pbmcgbW9kZWxzIGlzIHRoZSBgciBjb2xvcml6ZSgiQmFjay1Qcm9wYWdhdGlvbiBhbGdvcml0aG0iLCJyZWQiKWAgdG8gZ2V0IHRoZSBncmFkaWVudCBvZiB0aGUgbG9zcyBmdW5jdGlvbiB3aXRoIHJlc3BlY3QgdG8gdGhlIHBhcmFtZXRlci4KCi0gKipCYWNrLVByb3BhZ2F0aW9uKiogIGltcGxlbWVudHMgYHIgY29sb3JpemUoImJhY2t3YXJkIG1vZGUgYXV0b21hdGljIGRpZmZlcmVudGlhdGlvbiIsInJlZCIpYCAoc2VlIENoYXB0ZXIgNCBvZiBvdXIgYm9vaykKCi0gS2V5IHByaW5jaXBsZSBpcyB0byBleHBsYWluIHRoZSBgciBjb2xvcml6ZSgiY2hhaW4tcnVsZSIsInJlZCIpYCBhbmQgdG8gZ2V0IGEgKipyZWN1cnNpdmUgZXhwcmVzc2lvbioqIGZvciBncmFkaWVudCBmbG93LgoKLSBBIGtleSBlbGVtZW50cyBhcmUgYHIgY29sb3JpemUoImludGVybWVkaWF0ZSBkZXJpdmF0aXZlIHZhbHVlcyIsInJlZCIpYAoKXFsKXGRlbHRhXntbXGVsbF19IDo9IFxmcmFje1xwYXJ0aWFsIEMoYV57W0xdfSwgeSBcLCA7IFwsIFx0aGV0YSkgfXtccGFydGlhbCB6XntbXGVsbF19fSwKXHFxdWFkClxlbGwgPSAxLFxsZG90cyxMLApcXQoKCiFbXShCYWNrcHJvcC5wbmcpCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgpVc2luZyB0aGUgY2hhaW4tcnVsZToKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXFuOmRlbHRhLXJlY3Vyc2lvbi1tYW5pdH0KXGRlbHRhXntbXGVsbF19ID0gXGZyYWN7XHBhcnRpYWwgYV57W1xlbGxdfX17XHBhcnRpYWwgel57W1xlbGxdfX0KXGZyYWN7XHBhcnRpYWwgel57W1xlbGwrMV19fXtccGFydGlhbCBhXntbXGVsbF19fSAgClxmcmFje1xwYXJ0aWFsIEN9e1xwYXJ0aWFsIHpee1tcZWxsKzFdfX0KPVxmcmFje1xwYXJ0aWFsIGFee1tcZWxsXX19e1xwYXJ0aWFsIHpee1tcZWxsXX19XGZyYWN7XHBhcnRpYWwgel57W1xlbGwrMV19fXtccGFydGlhbCBhXntbXGVsbF19fSAgXGRlbHRhXntbXGVsbCsxXX0sClxxcXVhZCBcZWxsID0gTC0xLFxsZG90cywxLApcZW5ke2VxdWF0aW9ufQoKRm9yIHRoZSBmaW5hbCBsYXllciwgJFxlbGwgPSBMJCwgd2UgaGF2ZSwKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXFuOmxhc3QtZGVsdGF9ClxkZWx0YV57W0xdfSA9IFxmcmFje1xwYXJ0aWFsIGFee1tMXX19e1xwYXJ0aWFsIHpee1tMXX19XGZyYWN7XHBhcnRpYWwgQ317XHBhcnRpYWwgYV57W0xdfX0uClxlbmR7ZXF1YXRpb259CgotIEZpbmFsbHksIHVpc2luZyAkel57W1xlbGwrMV19PVdee1tcZWxsKzFdfWFee1tcZWxsXX0rYl57W1xlbGwrMV19JCwgd2UgaGF2ZQpcWwpcZnJhY3tccGFydGlhbCB6XntbXGVsbCsxXX19e1xwYXJ0aWFsIGFee1tcZWxsXX19PXtXXntbXGVsbCsxXX19Xlx0b3AuClxdCgo8L2Rpdj4KCgohW10oZmxvd19iYWNrcHJvcC5wbmcpCgoKCi0gQW4gZXhhbXBsZSBvbiBhIHNpbXBsZSBleGFtcGxlIGJ5IGhhbmQgaGVyZSBodHRwczovL2RlZXBsZWFybmluZ21hdGgub3JnL2dlbmVyYWwtZnVsbHktY29ubmVjdGVkLW5ldXJhbC1uZXR3b3Jrcy5odG1sCgoKIyMgVmFuaXNoaW5nIGFuZCBFeHBsb2RpbmcgR3JhZGllbnRzCgpUaGUga2V5IGJhY2sgcHJvcGFnYXRpb24gcmVjdXJzaW9uczoKCi0gKipmb3J3YXJkIHN0ZXAqKiAkYV57W1xlbGwrMV19ID0gU157W1xlbGxdfVxiaWcoV157W1xlbGxdfSBhXntbXGVsbC0xXX0gKyBiXntbXGVsbF19XGJpZykkICAKLSAqKmJhY2t3YXJkIHN0ZXAqKiAkXGRlbHRhXntbXGVsbF19ID0gXHRleHRybXtEaWFnfVxiaWcoXGRvdHtcc2lnbWF9XntbXGVsbF19KHpee1tcZWxsXX0pXGJpZykge1dee1tcZWxsKzFdfX1eXHRvcCBcZGVsdGFee1tcZWxsKzFdfSwkCgpGcm9tIGEgcHJhY3RpY2FsIHBlcnNwZWN0aXZlLCB0aGVzZSBzdGVwcyBhcmUgc29tZXRpbWVzIHN1YmplY3QgdG8gYHIgY29sb3JpemUoImluc3RhYmlsaXR5IiwicmVkIilgIHdoZW4gdGhlIG51bWJlciBvZiBsYXllcnMgJEwkIGlzIGxhcmdlLgoKCgoKLSBgciBjb2xvcml6ZSgiQnkgaWdub3JpbmcgdGhlIGFjdGl2YXRpb24gZnVuY3Rpb25zIiwicmVkIilgIGFuZCBhc3N1bWluZyB0aGF0ICRXXntbXGVsbF19JCBpcyB3aXRoIGEgYHIgY29sb3JpemUoImZpeGVkIHNxdWFyZSBkaW1lbnNpb24gYW5kIHRoZSBzYW1lIHdlaWdodCBtYXRyaXggVyIsInJlZCIpYCwgZm9yICRcZWxsID0gMSxcbGRvdHMsTC0xJCwgYW5kIHRoZSBsYXN0IHdlaWdodCBtYXRyaXggaXMgJFdee1tMXX0kLiAKCgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpyZXBhdGVkLW1hdHJpeC1zdHVmZn0KXGhhdHt5fT1hXntbTF19PVdee1tMXX0gV157W0wtMV19IFdee1tMLTJdfVxjZG90IFxsZG90cyBcY2RvdCBXXntbM119IFdee1syXX0gV157WzFdfSB4Cj1XXntbTF19IFdee0wtMX0geApcZW5ke2VxdWF0aW9ufQp3aGVyZSAkV157TC0xfSQgaXMgdGhlICRMLTEkIHBvd2VyIG9mICRXJC4gCgotIFVubGVzcyB0aGUgbWF4aW1hbCBlaWdlbnZhbHVlcyBvZiAkVyQgYXJlIGV4YWN0bHkgd2l0aCBhIG1hZ25pdHVkZSBvZiB1bml0eSwgYXMgJEwkIGdyb3dzIHdlIGhhdmUgdGhhdCAkXGhhdHt5fSQgZWl0aGVyIGByIGNvbG9yaXplKCJ2YW5pc2hlc3MiLCJyZWQiKWAgKHRvd2FyZHMgJDAkKSBvciBgciBjb2xvcml6ZSgiZXhwbG9kZXMiLCJyZWQiKWAgKHdpdGggdmFsdWVzIG9mIGluY3JlYXNpbmcgbWFnbml0dWRlKS4gCgotIElmICRXID0gd0kkIChhIGNvbnN0YW50IG11bHRpcGxlIG9mIHRoZSBpZGVudGl0eSBtYXRyaXgpLCB0aGVuICRcaGF0e3l9PVdee1tMXX0gd157TC0xfSB4JCwgYW5kIGZvciBhbnkgJHcgXG5lcSAxJCwgYSAqKnZhbmlzaGluZyoqICRcaGF0e3l9JCBvciAqKmV4cGxvZGluZyoqICRcaGF0e3l9JCBwaGVub21lbmEgcGVyc2lzdHMuIAoKLSBUaGlzIGlsbHVzdHJhdGlvbiBzaG93cyB0aGF0IGZvciBgciBjb2xvcml6ZSgibm9uLXNtYWxsIG5ldHdvcmsgIiwicmVkIilgIGRlcHRocyAobGFyZ2UgJEwkKSwgaW5zdGFiaWxpdHkgaXNzdWVzIG1heSBhcmlzZSBpbiB0aGUgYHIgY29sb3JpemUoImZvcndhcmQgcGFzcyAiLCJyZWQiKWAuIAoKCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgpTYW1lIHR5cGUgb2YgaW5zdGFiaWxpdHkgcHJvYmxlbSBjYW4gdGhlbiBhbHNvIHBlcnNpc3QgaW4gdGhlIGByIGNvbG9yaXplKCJiYWNrd2FyZCBwYXNzICIsInJlZCIpYCBzaW5jZSB0aGUgYmFja3dhcmQgcmVjdXJzaW9uICoqJFxkZWx0YV57W1xlbGxdfSA9XHRleHRybXtEaWFnfVxiaWcoXGRvdHtcc2lnbWF9XntbXGVsbF19KHpee1tcZWxsXX0pXGJpZykge1dee1tcZWxsKzFdfX1eXHRvcCBcZGVsdGFee1tcZWxsKzFdfSQgKiogYWxzbyBpbmNsdWRlcyByZXBlYXRlZCBtYXRyaXggbXVsdGlwbGljYXRpb25zLCBhbmQgaWYgZm9yIHNpbXBsaWNpdHkgd2UgaWdub3JlIHRoZSBhY3RpdmF0aW9uIGZ1bmN0aW9ucyBhbmQgYWdhaW4gdGFrZSBhIGNvbnN0YW50IG1hdHJpeCAkVyQsIHRoZW4sCgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpiYWNrLW1hdHJpeC1wb3dlci1wcm9ibGVtfQpcZGVsdGFee1tcZWxsXX0gPSBcQmlnKFdeXHRvcFxCaWcpXntMLVxlbGx9IFxkZWx0YV57W0xdfS4KXGVuZHtlcXVhdGlvbn0KCi0gVGhlcmUgaXMgb2Z0ZW4gYSB2YW5pc2hpbmcgb3IgZXhwbG9kaW5nIG5hdHVyZSBvZiAkXGRlbHRhXntbXGVsbF19JCBmb3IgbGFyZ2UgJEwkIGFuZCBsb3cgdmFsdWVzIG9mICRcZWxsJCAodGhlIGZpcnN0IGxheWVycyBvZiB0aGUgbmV0d29yaykuIAoKLSBUaGUgZ3JhZGllbnQgdmFsdWVzICRnX1dee1tcZWxsXX0kIGFuZCAkZ19iXntbXGVsbF19JCBtYXkgZ2V0IHNtYWxsZXIgYW5kIHNtYWxsZXIgYHIgY29sb3JpemUoIih2YW5pc2hpbmcpICIsImJsdWUiKWAgIG9yIGxhcmdlciBhbmQgbGFyZ2VyIGByIGNvbG9yaXplKCIoZXhwbG9kaW5nKSAiLCJibHVlIilgIGFzIHdlIGdvIGJhY2t3YXJkIHdpdGggZXZlcnkgbGF5ZXIgZHVyaW5nIGJhY2sgcHJvcGFnYXRpb24uIAoKLSBJbiB0aGUgd29yc3QgY2FzZSwgKip2YW5pc2hpbmcgZ3JhZGllbnRzKiosIG1heSBjb21wbGV0ZWx5IHN0b3AgdGhlIG5ldXJhbCBuZXR3b3JrIGZyb20gdHJhaW5pbmcsIG9yICoqZXhwbG9kaW5nIGdyYWRpZW50cyoqIG1heSB0aHJvdyBwYXJhbWV0ZXIgdmFsdWVzIHRvd2FyZHMgYXJiaXRyYXJ5IGRpcmVjdGlvbnMuIAoKLSBUaGlzIG1heSByZXN1bHQgaW4gYHIgY29sb3JpemUoIm9zY2lsbGF0aW9ucyAiLCJyZWQiKWAgYXJvdW5kIHRoZSBtaW5pbWEgb3IgZXZlbiBvdmVyc2hvb3RpbmcgdGhlIG9wdGltdW0gYWdhaW4gYW5kIGFnYWluLiAKCi0gQW5vdGhlciBpbXBhY3Qgb2YgKipleHBsb2RpbmcgZ3JhZGllbnRzKiogaXMgdGhhdCBodWdlIHZhbHVlcyBvZiB0aGUgZ3JhZGllbnRzIG1heSBjYXVzZSBudW1iZXIgb3ZlcmZsb3cgcmVzdWx0aW5nIGluIGluY29ycmVjdCBjb21wdXRhdGlvbnMgb3IgaW50cm9kdWN0aW9ucyBvZiBgciBjb2xvcml6ZSgiTkFOICIsInJlZCIpYCAoYGBub3QgYSBudW1iZXInJykuCgo8L2Rpdj4KCioqU29sdXRpb246KioKCi0gR3JhZGllbnQgZGVzY2VudCBpbXByb3ZlbWVudHMgc3VjaCBhcyBgciBjb2xvcml6ZSgiUk1TUHJvcCIsInJlZCIpYCwgYHIgY29sb3JpemUoIkFEQU0iLCJyZWQiKWAgY2FuIGhlbHAgbm9ybWFsaXplIHN1Y2ggdmFyaWF0aW9uIGluIHRoZSBncmFkaWVudHMuIE5ldmVydGhlbGVzcywgbnVtZXJpY2FsIGluc3RhYmlsaXR5IGNhbiBzdGlsbCBwZXJzaXN0LiAKCi0gRnVydGhlciwgd2l0aCBhY3RpdmF0aW9uIGZ1bmN0aW9ucyBzdWNoIGFzIGByIGNvbG9yaXplKCJzaWdtb2lkIiwicmVkIilgIG9yIGByIGNvbG9yaXplKCJ0YW5oIiwicmVkIilgLCBpbiBjYXNlcyBvZiBpbnB1dHMgZmFyIGZyb20gJDAkIHRoZSBncmFkaWVudCBjb21wb25lbnRzIG9mICRcdGV4dHJte0RpYWd9XGJpZyhcZG90e1xzaWdtYX1ee1tcZWxsXX0oel57W1xlbGxdfSlcYmlnKSQgbWF5IGFsc28gdmFuaXNoLgoKLSBBY3RpdmF0aW9uIGZ1bmN0aW9ucyBzdWNoIGFzIGByIGNvbG9yaXplKCJSZUxVIiwicmVkIilgIG9yIGByIGNvbG9yaXplKCJMZWFreSBSZUxVIiwicmVkIilgIGhhbmRsZSBzdWNoIHByb2JsZW1zLCB5ZXQgdGhlIG92ZXJhcmNoaW5nIHBoZW5vbWVub24gc3RpbGwgcGVyc2lzdHMuIAoKLSBPbmUgc3RyYXRlZ3kgZm9yIG1pdGlnYXRpbmcgc3VjaCBhIHByb2JsZW0gaXMgYmFzZWQgb24gYHIgY29sb3JpemUoIndlaWdodCBpbnRpYWxpemF0aW9uIiwicmVkIilgLgoKCgojIyBXZWlnaHQgSW5pdGlsaXphdGlvbgoKLSBTdGFydGluZyB3aXRoIGluaXRpYWwgdmFsdWVzIHRoYXQgYXJlIGVpdGhlciBjb25zdGFudCBvciAkMCQgZm9yIHRoZSB3ZWlnaHRzIGFuZCBiaWFzIHBhcmFtZXRlcnMgbWF5IHRocm93IHRoZSAqKmxlYXJuaW5nIHByb2Nlc3Mgb2ZmKiouCgotIFN1Y2ggY29uc3RhbnQgaW5pdGlhbCBwYXJhbWV0ZXJzIG1heSBpbXBvc2UgYHIgY29sb3JpemUoInN5bW1ldHJ5IiwicmVkIilgIG9uIHRoZSBhY3RpdmF0aW9uIHZhbHVlcyBvZiB0aGUgaGlkZGVuIHVuaXRzIGFuZCBpbiB0dXJuIHByb2hpYml0IHRoZSBtb2RlbCBmcm9tIGV4cGxvaXRpbmcgaXRzIGV4cHJlc3NpdmUgcG93ZXIuCgotIGByIGNvbG9yaXplKCJSYW5kb20gaW5pdGlhbGl6YXRpb24iLCJyZWQiKWAgZW5hYmxlcyB1cyB0byBicmVhayBhbnkgcG90ZW50aWFsIHN5bW1ldHJpZXMgYW5kIGlzIGFsbW9zdCBhbHdheXMgcHJlZmVyYWJsZS4gCgotICoqR2VuZXJhbCBwcmFjdGljZSoqOiB0aGUgbW9zdCBiYXNpYyByYW5kb20gaW50aWFsaXphdGlvbiBhcHByb2FjaCBpcyB0byBzZXQgYWxsIHBhcmFtZXRlcnMgb2YgdGhlIHdlaWdodCBtYXRyaWNlcyAkV157WzFdfSxcbGRvdHMsV157W0xdfSQgYXMgYHIgY29sb3JpemUoImluZGVwZW5kZW50IGFuZCBpZGVudGljYWxseSBkaXN0cmlidXRlZCBzdGFuZGFyZCBub3JtYWwgcmFuZG9tIHZhcmlhYmxlcyIsInJlZCIpYCBhbmQgdG8gc2V0IGFsbCB0aGUgZW50cmllcyBvZiB0aGUgYmlhcyB2ZWN0b3JzICRiXntbMV19LFxsZG90cyxiXntbTF19JCBhdCAkMCQuCgotIEEgbmljZSBhbmltYXRpb24gcG9zdCBvbiB0aGUgaW5mbHVlbmNlIG9mIHRoZSB3ZWlnaHQgaW5pdGlhbGl6YXRpb24gY291bGQgYmUgZm91bmQgaGVyZSBbSW5pdGlhbGl6aW5nIG5ldXJhbCBuZXR3b3Jrc10oaHR0cHM6Ly93d3cuZGVlcGxlYXJuaW5nLmFpL2FpLW5vdGVzL2luaXRpYWxpemF0aW9uL2luZGV4Lmh0bWwjSSkKCjxzdHlsZT4KZGl2LmJsdWUgeyBiYWNrZ3JvdW5kLWNvbG9yOiNlNmYwZmY7IGJvcmRlci1yYWRpdXM6IDVweDsgcGFkZGluZzogMjBweDt9Cjwvc3R5bGU+CjxkaXYgY2xhc3MgPSAiYmx1ZSI+CgotIEZvciBkZWVwIG5ldHdvcmtzLCBoZXVyaXN0aWMgdG8gaW5pdGlhbGl6ZSB0aGUgd2VpZ2h0cyBgciBjb2xvcml6ZSgiZGVwZW5kaW5nIG9uIHRoZSBub24tbGluZWFyIGFjdGl2YXRpb24gZnVuY3Rpb24iLCJyZWQiKWAgYXJlIGdlbmVyYWxseSB1c2VkLiBUaGUgbW9zdCBjb21tb24gcHJhY3RpY2UgaXMgdG8gZHJhdyB0aGUgZWxlbWVudCBvZiAgdGhlIG1hdHJpeCAkV157W2xdfSQgZnJvbSBub3JtYWwgZGlzdHJpYnV0aW9uIHdpdGggdmFyaWFuY2UgJGsvbV97bC0xfSQsIHdoZXJlICRrJCBkZXBlbmRzIG9uIHRoZSBhY3RpdmF0aW9uIGZ1bmN0aW9uLgoKLSBXaGlsZSB0aGVzZSBoZXVyaXN0aWNzIGRvIG5vdCBjb21wbGV0ZWx5IHNvbHZlIHRoZSBleHBsb2RpbmcvdmFuaXNoaW5nIGdyYWRpZW50cyBpc3N1ZSwgdGhleSBoZWxwIG1pdGlnYXRlIGl0IHRvIGEgZ3JlYXQgZXh0ZW50LiAKCi0gZm9yICRSZUxVJCBhY3RpdmF0aW9uOiAkaz0yJAoKLSBmb3IgJHRhbmgkIGFjdGl2YXRpb246ICRrPTEkLiBUaGUgaGV1cmlzdGljIGlzIGNhbGxlZCBgciBjb2xvcml6ZSgiWGF2aWVyIGluaXRpYWxpemF0aW9uIiwicmVkIilgLiAKCi0gQW5vdGhlciBjb21tb25seSB1c2VkIGhldXJpc3RpYyBpcyB0byBkcmF3IGZyb20gbm9ybWFsIGRpc3RyaWJ1dGlvbiB3aXRoIHZhcmlhbmNlICQyLyhtX3tsLTF9K21fbCkkCgo8L2Rpdj4KCiMjIEJhdGNoIE5vcm1hbGl6YXRpb24KCi0gYHIgY29sb3JpemUoIkJhdGNoIG5vcm1hbGl6YXRpb24iLCJyZWQiKWAgaXMgdG8gbm9ybWFsaXplIChvciBzdGFuZGFyZGl6ZSkgbm90IGp1c3QgdGhlIGlucHV0IGRhdGEgYnV0IGFsc28gaW5kaXZpZHVhbCBuZXVyb24gdmFsdWVzIHdpdGhpbiB0aGUgaW50ZXJtZWRpYXRlIGhpZGRlbiBsYXllcnMgb3IgZmluYWwgbGF5ZXIgb2YgdGhlIG5ldHdvcmsuIAoKLSBUYWtpbmcgJGokIGFzIGFuIGluZGV4IG9mIGEgbmV1cm9uIGluIGxheWVyICRcZWxsJCwgd2UgbWF5IHdpc2ggdG8gaGF2ZSBlaXRoZXIgJHpfal57W1xlbGxdfSQgb3IgJGFfal57W1xlbGxdfSQgZXhoaWJpdCBuZWFyLW5vcm1hbGl6ZWQgdmFsdWVzIG92ZXIgdGhlIGlucHV0IGRhdGFzZXQuIAoKLSBTdWNoIG5vcm1hbGl6YXRpb24gb2YgdGhlIG5ldXJvbiB2YWx1ZXMgdGhlbiB5aWVsZHMgbW9yZSBjb25zaXN0ZW50IHRyYWluaW5nIGFuZCBgciBjb2xvcml6ZSgibWl0aWdhdGVzIHZhbmlzaGluZyBvciBleHBsb2RpbmcgZ3JhZGllbnQiLCJibHVlIilgIHByb2JsZW1zLiBJdCBhbHNvIGhhcyBhIHNsaWdodCBgciBjb2xvcml6ZSgicmVndWxhcml6YXRpb24gZWZmZWN0IiwiYmx1ZSIpYCB3aGljaCBtYXkgcHJldmVudCBvdmVyZml0dGluZy4gCgoKLSBIZXJlIG91dGxpbmVzIG5vcm1hbGl6YXRpb24gb2YgdGhlICR6X2pee1tcZWxsXX0kIHZhbHVlcywgYnV0IG9uZSBtYXkgY2hvb3NlIHRvIGRvIHNvIGZvciB0aGUgJGFfal57W1xlbGxdfSQgdmFsdWVzIGluc3RlYWQuIAoKCiMjIERhdGEgUHJlcHJvY2Vzc2luZwoKXHNtYWxsIAoKLSAqKnN0YW5kYXJkaXphdGlvbiBvZiB0aGUgZGF0YSoqOiAgIHN1YnRyYWN0aW9uIG9mIHRoZSBtZWFuIG9mIGVhY2ggZmVhdHVyZSBhbmQgZGl2aXNpb24gYnkgdGhlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiB0aGUgZmVhdHVyZS4gCgotICoqc2FtcGxlIG1lYW4qKiBhbmQgKipzYW1wbGUgc3RhbmRhcmQgZGV2aWF0aW9uKioKClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOnN0YXRzLW1lYW4tdmFyfQpcb3ZlcmxpbmV7eH1faSA9IFxmcmFjezF9e259IFxzdW1fe2o9MX1ebiB4X2leeyhqKX0sClxxcXVhZApzXjJfaSA9IFxmcmFjezF9e259IFxzdW1fe2o9MX1ebiAoeF9pXnsoail9IC0gXG92ZXJsaW5le3h9X2kpXjIuClxlbmR7ZXF1YXRpb259CgotICoqc3RhbmRhcmRpemF0aW9uKiogCgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcTpyZWYtc3RhbmQten0Kel57KGopfV9pID0gXGZyYWN7eF57KGopfV9pIC0gXG92ZXJsaW5le3h9X2l9e3NfaX0KXHFxdWFkClx0ZXh0e2Zvcn0KXHFxdWFkCmo9MSxcbGRvdHMsbi4KXGVuZHtlcXVhdGlvbn0KCi0gRm9yIGZlYXR1cmUgJGkkLCAkel9pXnsoMSl9LCBcbGRvdHMsel9pXnsobil9JCwgaGFzIGEgc2FtcGxlIG1lYW4gb2YgZXhhY3RseSAkMCQgYW5kIGEgc2FtcGxlIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBleGFjdGx5ICQxJC4gCgotIFN1Y2ggc3RhbmRhcmRpemF0aW9uIGlzIHVzZWZ1bCBhcyBpdCBwbGFjZXMgdGhlIGR5bmFtaWMgcmFuZ2Ugb2YgdGhlIG1vZGVsIGlucHV0cyBvbiBhICoqdW5pZm9ybSBzY2FsZSoqIGFuZCB0aHVzIGltcHJvdmVzIHRoZSAqKm51bWVyaWNhbCBzdGFiaWxpdHkqKiBvZiBhbGdvcml0aG1zLgoKCgojIyBNaW4tTWF4IE5vcm1hbGl6YXRpb24gVGVjaG5pcXVlCgotICRcY29sb3J7Qmx1ZX17XHRleHRybXtNaW4tTWF4IE5vcm1hbGl6YXRpb259fSQgaXMgYW4gYW5vdGhlciBwcmVwcm9jZXNzaW5nIHRlY2huaXF1ZSB0byBzY2FsZSBudW1lcmljIGZlYXR1cmVzIGJldHdlZW4gMCBhbmQgMS4KCgokJAp6Xnsoail9X2kgPSBcZnJhY3t4Xnsoail9X2kgLSB4X2lee1x0ZXh0e21pbn19fXt4X2lee1x0ZXh0e21heH19LXhfaV57XHRleHR7bWlufX19ClxxcXVhZApcdGV4dHtmb3J9ClxxcXVhZApqPTEsXGxkb3RzLG4uCiQkCgp3aGVyZSAkeF9pXntcdGV4dHttaW59fSQgaXMgdGhlIG1pbmltdW0gdmFsdWUgb2YgdGhlICRpLXRoJCBmZWF0dXJlIGFuZCAkeF9pXntcdGV4dHttYXh9fSQgaXMgdGhlIG1heGltdW0gdmFsdWUgb2YgdGhlICRpLXRoJCBmZWF0dXJlCgotICoqU2NhbGUgSW52YXJpYW5jZSoqOiBQcmVzZXJ2ZXMgdGhlIHNoYXBlIG9mIHRoZSBvcmlnaW5hbCBkaXN0cmlidXRpb24uCgotICoqQ29udmVyZ2VuY2UqKjogSGVscHMgaW4gZmFzdGVyIGNvbnZlcmdlbmNlIGR1cmluZyB0cmFpbmluZy4KCi0gKipTdGFiaWxpdHkqKjogUmVkdWNlcyB0aGUgY2hhbmNlIG9mIHZhbmlzaGluZyBvciBleHBsb2RpbmcgZ3JhZGllbnRzLgoKCgoKCiMjIFRoZSBJZGVhIG9mIFBlciBVbml0IE5vcm1hbGl6YXRpb24KCgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgotIFRoZSBtYWluIGlkZWEgb2YgYmF0Y2ggbm9ybWFsaXphdGlvbiBpcyB0byBjb25zaWRlciBuZXVyb24gJGokIGluIGxheWVyICRcZWxsJCBhbmQgaW5zdGVhZCBvZiB1c2luZyAkel57W1xlbGxdfV9qJCAgdG8gdXNlIGEgdHJhbnNmb3JtZWQgdmVyc2lvbiAkXHRpbGRle3p9X2pee1tcZWxsXX0kLiAKCi0gU3VjaCBhIHRyYW5zZm9ybWF0aW9uIHRha2VzIHBsYWNlIGJvdGggaW4gdHJhaW5pbmcgdGltZSBhbmQgd2hlbiB1c2luZyB0aGUgbW9kZWwgaW4gcHJvZHVjdGlvbgoKLSBUaGUgdHJhbnNmb3JtYXRpb24gYWltcyB0byBwb3NpdGlvbiB0aGUgJFx0aWxkZXt6fV9qXntbXGVsbF19JCB2YWx1ZXMgc28gdGhhdCB0aGV5IGhhdmUgYHIgY29sb3JpemUoImFwcHJveGltYXRlbHkgemVybyBtZWFuIGFuZCB1bml0IHN0YW5kYXJkIGRldmlhdGlvbiBvdmVyIHRoZSBkYXRhIiwiYmx1ZSIpYC4gCgotIEZ1cnRoZXIsIHRoZSB0cmFuc2Zvcm1hdGlvbiBpbnZvbHZlcyBhIGNvcnJlY3Rpb24gdXNpbmcgdHJhaW5hYmxlIHBhcmFtZXRlcnMuCgotIER1cmluZyB0cmFpbmluZyB0aW1lLCBhdCBhIGdpdmVuIHRyYWluaW5nIGVwb2NoIGFuZCBmb3IgYSBnaXZlbiBtaW5pLWJhdGNoIG9mIHNpemUgJG5fYiQ6CgpcYmVnaW57ZXF1YXRpb259ClxsYWJlbHtlcS1ibS1tZWFuLXN0ZH0KXGhhdHtcbXV9X2pee1tcZWxsXX0gPVxmcmFjezF9e25fYn1cc3VtX3tpPTF9XntuX2J9el9qXntbXGVsbF0oaSl9IApccXF1YWQKXHRleHR7YW5kfQpccXF1YWQKXGhhdHtcc2lnbWF9X2pee1tcZWxsXX0gID0gXHNxcnR7IFxmcmFjezF9e25fYn1cc3VtX3tpPTF9XntuX2J9KHpfal57W1xlbGxdKGkpfS1caGF0e1xtdX1fal57W1xlbGxdfSleMn0sClxlbmR7ZXF1YXRpb259Cgp3aGVyZSAgJHpfal57W1xlbGxdKGkpfSQgaXMgdGhlIHZhbHVlIGF0IHVuaXQgJGokLCBhdCBsYXllciAkXGVsbCQsIGFuZCBzYW1wbGUgJGkkIHdpdGhpbiB0aGUgbWluaS1iYXRjaCwgcHJpb3IgdG8gY2Fycnlpbmcgb3V0IG5vcm1hbGl6YXRpb24uIAoKLSBXaXRoICRcaGF0e1xtdX1fal57W1xlbGxdfSQgYW5kICRcaGF0e1xzaWdtYX1fal57W1xlbGxdfSQgYXZhaWxhYmxlLCB3ZSBjb21wdXRlIAoKXGJlZ2lue2VxdWF0aW9ufQpcbGFiZWx7ZXE6Ym5vcm0xfQpcYmFye3p9X2pee1tcZWxsXShpKX09XGZyYWN7el9qXntbXGVsbF0oaSl9LVxoYXR7XG11fV9qXntbXGVsbF19fXtcc3FydHsoXGhhdHtcc2lnbWF9X2pee1tcZWxsXX0pXjIrXHZhcmVwc2lsb259fSwKXGVuZHtlcXVhdGlvbn0KCi0gQXQgdGhpcyBwb2ludCAkXGJhcnt6fV9qXntbXGVsbF0oaSl9JCBoYXMgbmVhcmx5IHplcm8gbWVhbiBhbmQgbmVhcmx5IHVuaXQgc3RhbmRhcmQgZGV2aWF0aW9uIGZvciBhbGwgZGF0YSBzYW1wbGVzICRpJCBpbiB0aGUgbWluaS1iYXRjaC4KCi0gYHIgY29sb3JpemUoIkFuIGFkZGl0aW9uYWwgdHJhbnNmb3JtYXRpb24gdGFrZXMgcGxhY2UgaW4gdGhlIGZvcm0iLCJibHVlIilgLAogClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOmJub3JtMn0KXHRpbGRle3p9X2pee1tcZWxsXShpKX09XGdhbW1hX2pee1tcZWxsXX0gXGJhcnt6fV9qXntbXGVsbF0oaSl9ICsgXGJldGFfal57W1xlbGxdfSwKXGVuZHtlcXVhdGlvbn0KCndoZXJlICRcZ2FtbWFfal57W1xlbGxdfSQgYW5kICRcYmV0YV9qXntbXGVsbF19JCBhcmUgdHJhaW5hYmxlIHBhcmFtZXRlcnMuIAoKLSAkXHRpbGRle3p9X2pee1tcZWxsXShpKX0kIGhhcyBhIHN0YW5kYXJkIGRldmlhdGlvbiBvZiBhcHByb3hpbWF0ZWx5ICRcZ2FtbWFfal57W1xlbGxdfSQgYW5kIGEgbWVhbiBvZiBhcHByb3hpbWF0ZWx5ICRcYmV0YV9qXntbXGVsbF19JCBvdmVyIHRoZSBkYXRhIHNhbXBsZXMgJGkkIGluIHRoZSBtaW5pLWJhdGNoLiAKCi0gVGhlc2UgcGFyYW1ldGVycyBhcmUgcmVzcGVjdGl2ZWx5IGluaXRpYWxpemVkIGF0ICQxJCBhbmQgJDAkLCBhbmQgdGhlbiBhcyB0cmFpbmluZyBwcm9ncmVzc2VzLCAkXGdhbW1hX2pee1tcZWxsXX0kIGFuZCAkXGJldGFfal57W1xlbGxdfSQgYXJlIHVwZGF0ZWQgdXNpbmcgdGhlIHNhbWUgbGVhcm5pbmcgbWVjaGFuaXNtcyBhcHBsaWVkIHRvIHRoZSB3ZWlnaHRzIGFuZCBiaWFzZXMgb2YgdGhlIG5ldHdvcmsuIAoKPC9kaXY+CgojIyBNaXRpZ2F0aW5nIE92ZXJmaXR0aW5nIHdpdGggRHJvcG91dCBhbmQgUmVndWxhcml6YXRpb24KCi0gRHJvcG91dCAKCi0gQWRkaXRpb24gb2YgUmVndWxhcml6YXRpb24gVGVybXMgYW5kIFdlaWdodCBEZWNheQoKCgojIyMgRHJvcG91dCAKCioqRHJvcG91dCoqIGlzIGEgcG9wdWxhciBhbmQgIGVmZmljaWVudCByZWd1bGFyaXphdGlvbiB0ZWNobmlxdWUuCgotIGByIGNvbG9yaXplKCJEcm9wb3V0IiwiYmx1ZSIpYCBpcyBhIHJlZ3VsYXJpemF0aW9uIHRlY2huaXF1ZSB3aGVyZSB3ZSAqKmR1cmluZyB0cmFpbmluZyoqIHJhbmRvbWx5IGRyb3AgdW5pdHMuCgotIFRoZSAgdGVybSAqKmRyb3BvdXQqKiAgcmVmZXJzICB0byAgZHJvcHBpbmcgIG91dCAgdW5pdHMgIChoaWRkZW4gIGFuZAp2aXNpYmxlKSBpbiBhIG5ldXJhbCBuZXR3b3JrLiAKCi0gQnkgZHJvcHBpbmcgYSB1bml0IG91dCwgbWVhbmluZyB0ZW1wb3JhcmlseSByZW1vdmVkIGl0IGZyb20gdGhlIG5ldHdvcmssIGFsb25nIHdpdGggYWxsIGl0cyBpbmNvbWluZyBhbmQgb3V0Z29pbmcgY29ubmVjdGlvbnMuCgotIFRoZSBjaG9pY2Ugb2Ygd2hpY2ggdW5pdHMgdG8gZHJvcCBpcyByYW5kb20uCgohW10oZHJvcG91dC5wbmcpCgotIEF0IGFueSBiYWNrIHByb3BhZ2F0aW9uIGl0ZXJhdGlvbiAoZm9yd2FyZCBwYXNzIGFuZCBiYWNrd2FyZCBwYXNzKSBvbiBhIG1pbmktYmF0Y2gsIG9ubHkgc29tZSByYW5kb20gc3Vic2V0IG9mIHRoZSBuZXVyb25zIGlzICoqYWN0aXZlKiouIFByYWN0aWNhbGx5IG5ldXJvbnMgaW4gbGF5ZXIgJFxlbGwkLCBmb3IgJFxlbGw9MCxcbGRvdHMsTC0xJCwgaGF2ZSBhIHNwZWNpZmllZCBwcm9iYWJpbGl0eSAkcF97XHRleHR7a2VlcH19XntbXGVsbF19IFxpbiAoMCwxXSQgd2hlcmUgaWYgJHBfe1x0ZXh0e2tlZXB9fV57W1xlbGxdfSA9IDEkIGRyb3BvdXQgZG9lcyBub3QgYWZmZWN0IHRoZSBsYXllciwgYW5kIG90aGVyd2lzZSBlYWNoIG5ldXJvbiAkaSQgb2YgdGhlIGxheWVyIGlzIGBgZHJvcHBlZCBvdXQnJyB3aXRoIHByb2JhYmlsaXR5ICQxIC0gcF97XHRleHR7a2VlcH19XntbXGVsbF19JC4gCgotIGByIGNvbG9yaXplKCJJbiB0aGUgYmFja3dhcmQgcGFzczoiLCJibHVlIilgIHdoZW4gbmV1cm9uICRpJCBpcyBkcm9wcGVkIG91dCBpbiBsYXllciAkXGVsbCQsIHRoZSB3ZWlnaHRzICR3X3tpLGp9XntbXGVsbCsxXX0kIGZvciBhbGwgbmV1cm9ucyAkaj0xLFxsZG90cyxOX3tcZWxsKzF9JCBhcmUgdXBkYXRlZCBiYXNlZCBvbiB0aGUgZ3JhZGllbnQgJFtnX1dee1tcZWxsXX1dX3tpan0kIHdoaWNoIGlzIHNldCBhdCAkMCQuIAoKLSBXaXRoIGEgYHIgY29sb3JpemUoInB1cmUgZ3JhZGllbnQgZGVzY2VudCBvcHRpbWl6ZXIiLCJibHVlIilgIHRoaXMgbWVhbnMgdGhhdCB3ZWlnaHRzICR3X3tpLGp9XntbXGVsbCsxXX0kIGFyZSBub3QgdXBkYXRlZCBhdCBhbGwgZHVyaW5nIHRoZSBnaXZlbiBpdGVyYXRpb24sIHdoZXJlYXMgd2l0aCBhIG1vbWVudHVtIGJhc2VkIG9wdGltaXplciBzdWNoIGFzIGByIGNvbG9yaXplKCJBREFNIiwicmVkIilgIGl0IG1lYW5zIHRoYXQgdGhlIGRlc2NlbnQgc3RlcCBmb3IgdGhvc2Ugd2VpZ2h0cyBoYXMgYSBgciBjb2xvcml6ZSgic21hbGxlciBtYWduaXR1ZGUiLCJyZWQiKWAuCgotIEluIHByYWN0aWNlLCB0aGlzIHNpbXBsZSBhbmQgZWFzeSBpZGVhIG9mIGRyb3BvdXQgaGFzIGByIGNvbG9yaXplKCJpbXByb3ZlZCBwZXJmb3JtYW5jZSIsInJlZCIpYCBvZiBkZWVwIG5ldXJhbCBuZXR3b3JrcyBpbiBtYW55IGVtcGlyaWNhbGx5IHRlc3RlZCBjYXNlcy4gSXQgaXMgbm93IGFuIGludGVncmFsIHBhcnQgb2YgZGVlcCBsZWFybmluZyB0cmFpbmluZy4gCgoKCiMjIFZpZXdpbmcgRHJvcG91dCBhcyBhbiBFbnNlbWJsZSBBcHByb3hpbWF0aW9uIAoKLSBEcm9wb3V0IGNhbiBiZSB2aWV3ZWQgYXMgYW4gYXBwcm94aW1hdGlvbiBvZiBhbiAkXGNvbG9ye1JlZH17XHRleHRybXtlbnNlbWJsZSBtZXRob2R9fSQsIGEgZ2VuZXJhbCBjb25jZXB0IGZyb20gbWFjaGluZSBsZWFybmluZy4gCgotIFdoYXQgaXMgJFxjb2xvcntSZWR9e1x0ZXh0cm17ZW5zZW1ibGUgbGVhcm5pbmd9fSQgPwoKLSBXaGVuIHdlIHNlZWsgYSBtb2RlbCAkXGhhdHt5fSA9IGZfXHRoZXRhKHgpJCwgd2UgbWF5IHVzZSB0aGUgc2FtZSBkYXRhc2V0IHRvIHRyYWluIG11bHRpcGxlIG1vZGVscyB0aGF0IGFsbCB0cnkgdG8gYWNoaWV2ZSB0aGUgc2FtZSB0YXNrLgoKLSBXZSBtYXkgdGhlbiBjb21iaW5lIHRoZSBtb2RlbHMgaW50byBhbiAgJFxjb2xvcntCbHVlfXtcdGV4dHJte2Vuc2VtYmxlfX0kIChtb2RlbCkuCgotIFRoZSBcdGV4dGNvbG9ye2JsdWV9e2xhdHRlcn0gaXMgdXN1YWxseSAkXGNvbG9ye1JlZH17XHRleHRybXttb3JlIGFjY3VyYXRlfX0kIHRoYW4gZWFjaCBvZiB0aGUgaW5kaXZpZHVhbCBtb2RlbHMuIAoKIyMgRW5zZW1ibGUgbGVhcm5pbmcKCi0gQ29uc2lkZXIgIGEgc2NhbGFyIG91dHB1dCBtb2RlbC4gCgotIFdlIHVzZSAkXGNvbG9ye0JsdWV9e01cIFx0ZXh0cm17bW9kZWxzfX0kOiAkXGhhdHt5fV57XHsgaSBcfSB9ID0gZl97XHRoZXRhX3tce2lcfX19Xntce2lcfX0oeCkkIGZvciAkaT0xLFxsZG90cyxNJCwgd2hlcmUgJFx0aGV0YV97XHtpXH19JCBpcyB0YWtlbiBoZXJlIGFzIHRoZSBzZXQgb2YgIHBhcmFtZXRlcnMgb2YgdGhlICRpJC10aCBtb2RlbC4gCgotIFRoZSBlbnNlbWJsZSBtb2RlbCBvbiBhbiBpbnB1dCAkeCQgaXMgdGhlbiAkXGNvbG9ye1JlZH17XHRleHRybXt0aGUgYXZlcmFnZX19JCwKXFsKZl97XHRoZXRhfSh4KSAgPSBcZnJhY3sxfXtNfSBcc3VtX3tpPTF9Xk0gZl97XHRoZXRhX3tce2lcfX19XntceyBpXH19KHgpLApccXF1YWQKXHRleHR7d2hlcmV9ClxxcXVhZApcdGhldGEgPSAoXHRoZXRhX3tcezFcfX0sXGxkb3RzLFx0aGV0YV97XHtNXH19KS4KXF0KCi0gJGZfXHRoZXRhKFxjZG90KSQgaXMgbW9yZSBjb21wdXRhdGlvbmFsbHkgY29zdGx5IHNpbmNlIGl0IHJlcXVpcmVzICRNJCBtb2RlbHMgaW5zdGVhZCBvZiBhIHNpbmdsZSBtb2RlbC4KCi0gTmV2ZXJ0aGVsZXNzLCB0aGVyZSBhcmUgYmVuZWZpdHMuCgoKLSBBc3N1bWUgIHRoZSBtb2RlbHMgYXJlICRcY29sb3J7UmVkfXtcdGV4dHJte2hvbW9nZW5vdXN9fSQgaW4gbmF0dXJlIGFuZCBvbmx5IGRpZmZlciBkdWUgdG8gcmFuZG9tbmVzcyBpbiB0aGUgdHJhaW5pbmcgcHJvY2VzcyBhbmQgbm90IHRoZSBtb2RlbCBjaG9pY2Ugb3IgaHlwZXItcGFyYW1ldGVycy4gCgotIEZvciBzb21lICRcY29sb3J7Qmx1ZX17XHRleHRybXtmaXhlZCB1bnNlZW4gaW5wdXQgfVx0aWxkZXt4fX0kIHdlIG1heSB0cmVhdCB0aGUgb3V0cHV0IG9mIG1vZGVsICRpJCwgZGVub3RlZCAkXGhhdHt5fV57XHtpXH19X3tcdGhldGFfe1x7aVx9fX0oXHRpbGRle3h9KSQsIGFzIGEgJFxjb2xvcntSZWR9e1x0ZXh0cm17cmFuZG9tIHZhcmlhYmxlIHRoYXQgaXMgaWRlbnRpY2FsIGluIGRpc3RyaWJ1dGlvbiB0byBldmVyeSBvdGhlciBtb2RlbCBvdXRwdXQgfSBcaGF0e3l9Xntce2pcfX1fe1x0aGV0YV97XHtqXH19fShcdGlsZGV7eH0pfSQsIHlldCBnZW5lcmFsbHkgbm90IGluZGVwZW5kZW50LgoKLSBXZSBmdXJ0aGVyIGFzc3VtZSB0aGF0IGFueSBwYWlyIG9mIG1vZGVsIG91dHB1dHMgaXMgJFxjb2xvcntSZWR9e1x0ZXh0cm17aWRlbnRpY2FsbHkgZGlzdHJpYnV0ZWQgdG8gYW55IG90aGVyIHBhaXJ9fSQ6IApcWwpcbWF0aGJie0V9XGJpZ1tcaGF0e3l9X3tcdGhldGFfe1x7aVx9fX1ee1x7aVx9fShcdGlsZGV7eH0pXGJpZ10gPSBcbXUsClxxcXVhZApcdGV4dHJte1Zhcn1cYmlnKFxoYXR7eX1fe1x0aGV0YV97XHtpXH19fV57XHtpXH19KFx0aWxkZXt4fSlcYmlnKSA9IFxzaWdtYV4yLApccXF1YWQKXHRleHR7YW5kfQpccXF1YWQKXHRleHR7Y29yfVxiaWcoXGhhdHt5fV97XHRoZXRhX3tce2lcfX19Xntce2lcfX0oXHRpbGRle3h9KSwgXGhhdHt5fV97XHRoZXRhX3tce2pcfX19Xntce2pcfX0oXHRpbGRle3h9KVxiaWcpID0gXHJobywKXF0Kd2hlcmUgJFx0ZXh0e2Nvcn0oXGNkb3QsIFxjZG90KSQgaXMgdGhlIGNvcnJlbGF0aW9uIGJldHdlZW4gdHdvIG1vZGVscyAkaSBcbmVxIGokIGFuZCBpcyBhc3N1bWVkIHRvIGJlIHRoZSBzYW1lIGZvciBhbGwgJGkkLCAkaiQgcGFpcnMuCgo8ZGl2IGNsYXNzID0gIm9yYW5nZSI+CgotIFN1Y2ggJFxjb2xvcntCbHVlfXtcdGV4dHJte2FuIGFzc3VtcHRpb24gb24gdGhlIGNvcnJlbGF0aW9ufX0kIGFsc28gaW1wb3NlcyBhIGxvd2VyIGJvdW5kIG9uIHRoZSBjb3JyZWxhdGlvbiwKClxbCiAtXGZyYWN7MX17TS0xfSBcbGUgXHJobywKXHFxdWFkClx0ZXh0e29yfQpccXF1YWQKMCBcbGUgXHJobyArIFxmcmFjezEtXHJob317TX0uClxdCgoKLSBOb3cgZXZhbHVhdGUgdGhlICRcY29sb3J7Qmx1ZX17XHRleHRybXttZWFufX0kIGFuZCAkXGNvbG9ye0JsdWV9e1x0ZXh0cm17dmFyaWFuY2V9fSQgb2YgdGhlIGVuc2VtYmxlIG1vZGVsOgoKXFsKXG1hdGhiYntFfVtmX3tcdGhldGF9KFx0aWxkZXt4fSldICA9IFxmcmFjezF9e019IFxtYXRoYmJ7RX0gXGJpZ1tcc3VtX3tpPTF9Xk0gZl97XHRoZXRhX3tce2lcfX19Xntce2lcfX0oXHRpbGRle3h9KSBcYmlnXSA9IFxtdSwKXF0KCmFuZCBmdXJ0aGVyIG5vdGluZyB0aGF0ICRccmhvIFxzaWdtYV4yJCBpcyB0aGUgJFxjb2xvcntCbHVlfXtcdGV4dHJte2NvdmFyaWFuY2UgYmV0d2VlbiBhbnkgdHdvIG1vZGVsc319JCB3ZSBvYnRhaW4KClxbClx0ZXh0cm17VmFyfVxiaWcoIGZfe1x0aGV0YX0oXHRpbGRle3h9KSBcYmlnKSAgPSBcZnJhY3sxfXtNXjJ9IFx0ZXh0cm17VmFyfVxCaWcoXHN1bV97aT0xfV5NIGZfe1x0aGV0YV97XHtpXH19fV57XHtpXH19KFx0aWxkZXt4fSlcQmlnKSA9IFxmcmFjezF9e01eMn0gXGJpZyhNIFxzaWdtYV4yICsgIE0oTS0xKSBccmhvIFxzaWdtYV4yIFxiaWcpID0gXEJpZyggXHJobyArIFxmcmFjezEtXHJob317TX1cQmlnKVxzaWdtYV4yLgpcXQoKPC9kaXY+CgotIEFzIHRoZSBudW1iZXIgb2YgbW9kZWxzIGluIHRoZSBlbnNlbWJsZSwgJE0kLCBncm93cywgdGhlIHZhcmlhbmNlIG9mIHRoZSBlbnNlbWJsZSBtb2RlbCAkXGNvbG9ye0JsdWV9e1x0ZXh0cm17Y29udmVyZ2VzfX0kIHRvICRccmhvIFxzaWdtYV4yJC4gCgoKLSBTaW5jZSAkXHJobyBcbGUgMSQgYW5kIHByYWN0aWNhbGx5ICRccmhvIDwgMSQsIHRoaXMgJFxjb2xvcntCbHVlfXtcdGV4dHJte2xpbWl0aW5nIHZhcmlhbmNlfX0kIGlzIGxlc3MgdGhhbiAkXHNpZ21hXjIkLiAKCi0gRm9yIGV4YW1wbGUgaWYgJFxyaG8gPSAwLjUkIGFzICRNJCBncm93cyB0aGUgJFxjb2xvcntSZWR9e1x0ZXh0cm17dmFyaWFuY2Ugb2YgdGhlIGVzdGltYXRvciBkcm9wc319JCBieSAkNTBcJSQuCgotIFRoZXNlIHByb3BlcnRpZXMgb2YgZW5zZW1ibGUgbW9kZWxzIG1ha2UgdGhlbSAkXGNvbG9ye0JsdWV9e1x0ZXh0cm17dmVyeSBhdHRyYWN0aXZlfX0kIGJlY2F1c2UgdGhlIGJpYXMgZG9lcyBub3QgY2hhbmdlIGJ1dCB0aGUgdmFyaWFuY2UgZGVjcmVhc2VzCgotIE5ldmVydGhlbGVzcywgZGVlcCBsZWFybmluZyBtb2RlbHMgJFxjb2xvcntSZWR9e1x0ZXh0cm17YXJlIG5vdCBlYXNpbHkgYW1lbmFibGV9fSQgZm9yIGVuc2VtYmxlIG1vZGVscyBiZWNhdXNlIHRoZSBudW1iZXIgb2YgcGFyYW1ldGVycyBhbmQgY29tcHV0YXRpb25hbCBjb3N0IChib3RoIGZvciB0cmFpbmluZyBhbmQgcHJvZHVjdGlvbikgaXMgdG9vIGhpZ2guCgotIFRyYWluaW5nIGEgc2luZ2xlIG1vZGVsIG1heSBzb21ldGltZXMgJFxjb2xvcntCbHVlfXtcdGV4dHJte3Rha2UgZGF5cyBhbmQgdGhlIGNvbXB1dGF0aW9uYWwgY29zdHMgb2YgYSBzaW5nbGUgZXZhbHVhdGlvbiB9IGZfe1x0aGV0YV97XHtpXH19fV57XHtpXH19KFx0aWxkZXt4fSl9JCBhcmUgYWxzbyBub24tbmVnbGlnaWJsZX0uCgotIFRoaXMgaXMgd2hlcmUgJFxjb2xvcntSZWR9e1x0ZXh0cm17ZHJvcG91dCBjb21lcyBpbn19JC4KCi0tLQoKIyMjIFZpZXdpbmcgRHJvcG91dCBhcyBhbiBFbnNlbWJsZSBBcHByb3hpbWF0aW9uIAoKCiBXZSBtYXkgJFxjb2xvcntSZWR9e1x0ZXh0cm17bG9vc2VseSB2aWV3IGRyb3BvdXQgYXMgYW4gZW5zZW1ibGUgb2YgfSBNIFx0ZXh0cm17IG1vZGVsc319JCB3aGVyZSAkTSQgaXMgdGhlIG51bWJlciBvZiB0cmFpbmluZyBpdGVyYXRpb25zLiAKCi0gMXN0IGl0ZXJhdGlvbjogIEtlZXAgYW5kIHVwZGF0ZSBlYWNoIHVuaXQgd2l0aCBwcm9iYWJpbGl0eSAkcCQgKCRwX3trZWVwfSQpLCBkcm9wIHJlbWFuZGluZyBvbmVzCi0gMnN0IGl0ZXJhdGlvbjogIEtlZXAgYW5kIHVwZGF0ZSBhbm90aGVyIHJhbmRvbSBzZWxlY3Rpb24gb2YgdW5pdHMsIGRyb3AgcmVtYW5kaW5nIHVuaXRzLgotICoqJHQkdGggaXRlcioqIENvbnRpbnVlIGluIHRoZSBzYW1lIG1hbm5lci4KLSAqKlRlc3QgdGltZSoqIFVzZSBhbGwgdW5pdHMuIFdlaWdodCBtdWx0aXBsaWVkIGJ5ICRwJC4KCgohW10oaW1hZ2VzL2Ryb3BvdXRfcHJpbmNpcGxlLnBuZykKCgoKIyMgVmlld2luZyBEcm9wb3V0IGFzIGFuIEVuc2VtYmxlIEFwcHJveGltYXRpb24KCgoxKSBGb3IgYSBuZXVyYWwgbmV0d29yayB3aXRoICRNJCB1bml0cyB0aGVyZSBhcmUgJDJeTSQgcG9zc2libGUKdGhpbm5lZCBuZXVyYWwgbmV0d29ya3MuIENvbnNpZGVyIHRoaXMgYXMgb3VyICRcY29sb3J7UmVkfXtcdGV4dHJte2Vuc2VtYmxlfX0kLgoKIVtdKGltYWdlcy9lc2FtYmxibGVkcm9wb3V0LnBuZykKCgotICRcY29sb3J7Qmx1ZX17XHRleHRybXtBcHByb3hpbWF0aW9uIDE6fX0kIEF0IGVhY2ggaXRlcmF0aW9uIHdlIHNhbXBsZSBvbmUgZW5zZW1ibGUKbWVtYmVyIGFuZCB1cGRhdGUgaXQuIE1vc3Qgb2YgdGhlIG5ldHdvcmtzIHdpbGwgbmV2ZXIgYmUgdXBkYXRlZCBzaW5jZSAkMl5NPj4kIHRoZSBudW1iZXIgb2YgaXRlcmF0aW9ucy4KCi0tLQoKMikgQXQgdGVzdCB0aW1lIHdlIHdvdWxkIG5lZWQgdG8gYXZlcmFnZSBvdmVyIGFsbCAkMl5NJCB3aGljaCBpcyBub3QKZmVhc2libGUgd2hlbiAkMl5NJCBpcyBodWdlLgoKLSAkXGNvbG9ye0JsdWV9e1x0ZXh0cm17QXBwcm94aW1hdGlvbiAyOn19JCBJbnN0ZWFkLCBhdCB0ZXN0IHRpbWUgd2UgZXZhbHVhdGUgdGhlIGZ1bGwKbmV1cmFsIG5ldHdvcmsgd2hlcmUgdGhlIHdlaWdodCBhcmUgbXVsdGlwbGllZCBieSAkcCQuCgohW10oaW1hZ2VzL2FwcHJveGltYXRpb24yLnBuZykKCgpJdCBoYXMgYmVlbiBlbXBpcmljYWxseSBzaG93biB0aGF0IHRoaXMgaXMgYSBnb29kIGFwcHJveGltYXRpb24gb2YKdGhlIGF2ZXJhZ2Ugb2YgYWxsIGVuc2VtYmxlIG1lbWJlcnMuCgoKCgojIyMgQWRkaXRpb24gb2YgUmVndWxhcml6YXRpb24gVGVybXMgYW5kIFdlaWdodCBEZWNheQoKLSBgciBjb2xvcml6ZSgiQWRkaXRpb24gb2YgYSByZWd1bGFyaXphdGlvbiIsInJlZCIpYCB0ZXJtIGlzIGFub3RoZXIga2V5IGFwcHJvYWNoIHRvIHByZXZlbnQgb3ZlcmZpdHRpbmcgYW5kIGltcHJvdmUgZ2VuZXJhbGl6YXRpb24gcGVyZm9ybWFuY2UuIAoKLSBBdWdtZW50aW5nIHRoZSBsb3NzIHdpdGggYSByZWd1bGFyaXphdGlvbiB0ZXJtICRSX1xsYW1iZGEoXHRoZXRhKSQgcmVzdHJpY3RzIHRoZSBmbGV4aWJpbGl0eSBvZiB0aGUgbW9kZWwsIGFuZCB0aGlzIHJlc3RyaWN0aW9uIGlzIHNvbWV0aW1lcyBuZWVkZWQgdG8gcHJldmVudCBvdmVyLWZpdHRpbmcuIAoKLSBJbiB0aGUgY29udGV4dCBvZiBkZWVwIGxlYXJuaW5nLCBhbmQgZXNwZWNpYWxseSB3aGVuIGByIGNvbG9yaXplKCJyaWRnZSByZWdyZXNzaW9uIiwiYmx1ZSIpYCBzdHlsZSByZWd1bGFyaXphdGlvbiBpcyBhcHBsaWVkLCB0aGlzIHByYWN0aWNlIGlzIHNvbWV0aW1lcyBjYWxsZWQgYHIgY29sb3JpemUoIndlaWdodCBkZWNheSIsImJsdWUiKWAgd2hlbiBjb25zaWRlcmluZyBncmFkaWVudCBiYXNlZCBvcHRpbWl6YXRpb24uIAoKLSBUYWtlIHRoZSBvcmlnaW5hbCBsb3NzIGZ1bmN0aW9uICRDKFx0aGV0YSkkIGFuZCBhdWdtZW50IGl0IHRvIGJlICRcdGlsZGV7Q30oXHRoZXRhKSA9IEMoXHRoZXRhKSArIFJfXGxhbWJkYShcdGhldGEpJC4gCgo8c3R5bGU+CmRpdi5ibHVlIHsgYmFja2dyb3VuZC1jb2xvcjojZTZmMGZmOyBib3JkZXItcmFkaXVzOiA1cHg7IHBhZGRpbmc6IDIwcHg7fQo8L3N0eWxlPgo8ZGl2IGNsYXNzID0gImJsdWUiPgoKLSBMZXQgdXMgZm9jdXMgb24gdGhlIHJpZGdlIHJlZ3Jlc3Npb24gdHlwZSByZWd1bGFyaXphdGlvbiB3aXRoIHBhcmFtZXRlciAkXGxhbWJkYSA+MCQsIGFuZCwKClxbClJfXGxhbWJkYShcdGhldGEpID0gXGZyYWN7XGxhbWJkYX17Mn0gUihcdGhldGEpLApccXF1YWQKXHRleHR7d2l0aH0KXHFxdWFkClIoXHRoZXRhKSA9IFx8IFx0aGV0YSBcfF4yID0gXHRoZXRhXzFeMiArIFxsZG90cyArIFx0aGV0YV9kXjIuClxdCgotIEhlcmUgZm9yIG5vdGF0aW9uYWwgc2ltcGxpY2l0eSB3ZSBzaW1wbHkgY29uc2lkZXIgYWxsIHRoZSAkZCQgcGFyYW1ldGVycyBvZiB0aGUgbW9kZWwgYXMgc2NhbGFycywgJFx0aGV0YV9pJCBmb3IgJGk9MSxcbGRvdHMsZCQgCgoKLSBOb3cgZm9yIHNpbXBsaWNpdHksIGFzc3VtZSB3ZSBleGVjdXRlIGJhc2ljIGdyYWRpZW50IGRlc2NlbnQgc3RlcHMuIAoKLSBXaXRoIGEgbGVhcm5pbmcgcmF0ZSAkXGFscGhhID4gMCQsIHRoZSB1cGRhdGUgYXQgaXRlcmF0aW9uICR0JCBpcywKClxbClx0aGV0YV57KHQrMSl9ID0gXHRoZXRhXnsodCl9IC0gXGFscGhhIFxuYWJsYSBcdGlsZGV7Q30oXHRoZXRhXnsodCl9KS4KXF0KCgpJbiBvdXIgcmlkZ2UgcmVncmVzc2lvbiBzdHlsZSBwZW5hbHR5IGNhc2Ugd2UgaGF2ZSAkXG5hYmxhIFx0aWxkZXtDfShcdGhldGEpID0gXG5hYmxhIHtDfShcdGhldGEpICsgXGxhbWJkYSBcdGhldGEkLCBhbmQgaGVuY2UgdGhlIGdyYWRpZW50IGRlc2NlbnQgdXBkYXRlIGNhbiBiZSByZXByZXNlbnRlZCBhcyAKClxiZWdpbntlcXVhdGlvbn0KXGxhYmVse2VxOndlaWdodC1kZWNheX0KXHRoZXRhXnsodCsxKX0gPSAoMS0gXGFscGhhIFxsYW1iZGEpIFx0aGV0YV57KHQpfSAtIFxhbHBoYSBcbmFibGEge0N9KFx0aGV0YV57KHQpfSkuClxlbmR7ZXF1YXRpb259CgpBc3N1bWluZyB0aGF0ICRcYWxwaGEgXGxhbWJkYSA8IDIkLCBpcyB0aGF0IGl0IGludm9sdmVzIHNocmlua2FnZSBvciB3ZWlnaHQgZGVjYXkgZGlyZWN0bHkgb24gdGhlIHBhcmFtZXRlcnMgaW4gYWRkaXRpb24gdG8gZ3JhZGllbnQgYmFzZWQgbGVhcm5pbmcuIAoKLSBUaGF0IGlzLCBpbmRlcGVuZGVudGx5IG9mIHRoZSB2YWx1ZSBvZiB0aGUgZ3JhZGllbnQgJFxuYWJsYSB7Q30oXHRoZXRhXnsodCl9KSQsIGluIGV2ZXJ5IGl0ZXJhdGlvbiwgdGhlIHVwZGF0ZSBjb250aW51ZXMgdG8gZGVjYXkgdGhlIHBhcmFtZXRlcnMsIGVhY2ggdGltZSBtdWx0aXBseWluZyB0aGUgcHJldmlvdXMgcGFyYW1ldGVyIGJ5IGEgZmFjdG9yICQxLVxhbHBoYSBcbGFtYmRhJC4gCgo8L2Rpdj4KCi0tLQoKPGRpdiBjbGFzcyA9ICJvcmFuZ2UiPgoKKipQcmFjdGljZSA0OioqIEluIHRoaXMgcHJhY3RpY2UsIHdlIG1haW5seSB1c2UgdGhlIE1OSVNUIGRhdGFzZXQgdG8gZXhwbG9yZSBjbGFzc2lmaWNhdGlvbiBkZWVwIG5ldXJhbCBuZXR3b3JrcyAoRE5OKSBtb2RlbHMuCkF0IHRoZSBlbmQgb2YgdGhpcyBwcmFjdGljZSwgeW91IHNob3VsZCBiZSBjb21mb3J0YWJsZSB0byB1c2UgYSBzb2Z0d2FyZSBwYWNrYWdlIChoZXJlICoqa2VyYXMqKikgdG8gcnVuIGRpZmZlcmVudCBtb2RlbHMgZm9yIGEgY2xhc3NpZmljYXRpb24gdGFzay4gIFlvdSB3aWxsIGV4cGxvcmUgZGlmZmVyZW50IG1vZGVscyBieSBleHBsb3JpbmcvdHVuaW5nIGRpZmZlcmVudCBoeXBlcnBhcmFtYXRlcnMgb2YgdGhlIEROTjogCgotIG51bWJlciBvZiBsYXllcnMgYW5kIG5vZGVzCi0gYmF0Y2ggbm9ybWFsaXphdGlvbgotIHJlZ3VsYXJpemF0aW9uIHRlY2huaXF1ZQotIGRyb3BvdXQgCi0gd2VpZ2h0IGluaXRpYWxpemF0aW9uCgpbRGVlcCBOZXVyYWwgbmV0d29yazogUHJhY3RpY2UgNF0oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2RyaXZlLzFNc2cyR0R0NVAwa0IwTVZQU1BVY0daR0l4RGpPZ29EYj91c3A9c2hhcmluZykgd2l0aCBQeXRvbiAoZ29vZ2xlIGNvbGxhYikKCltUdXRvcmlhbCBvbiBNTklTVCBkYXRhXShQUkFDVElDRS9UdXRvcmlhbC1NTklTVC5odG1sKSB1c2luZyBSIGNvZGUgKG1pZ2h0IGJlIHNsb3cpCgo8L2Rpdj4KCiMgUHJhY3RpY2UgYW5kIElsbHVzdHJhdGlvbiBpbiBEZWVwIExlYXJuaW5nCgoKIyMjIHdpdGggUiBjb2RlCgotIFtCaW5hcnkgQ2xhc3NpZmljYXRpb24gdGFza10oUFJBQ1RJQ0UvSWxsdXN0cmF0aW9uLUJpbmFyeS1jbGFzc2lmaWNhdGlvbi10YXNrLmh0bWwpCi0gW1ByZWRpY3Rpb24gdGFza10oUFJBQ1RJQ0UvaWxsdXN0cmF0aW9uLVNoYWxsb3ctTk4uaHRtbCkKLSBbQXBwcm94aW1hdGlvbiBBYmlsbGl0eV0oUFJBQ1RJQ0UvSWxsdXN0cmF0aW9uX0FwcHJveF9BYmlsaXR5Lmh0bWwpCi0gW0V4cHJlc3Npdml0eSBleGFtcGxlXShQUkFDVElDRS9JbGx1c3RyYXRpb24tTm9uLWxpbmVhci1idW5kYXJ5Lmh0bWwpCi0gW0lsbHVzdHJhdGlvbiBPdmVyZml0XShQUkFDVElDRS9JbGx1c3RyYXRpb25fT3ZlcmZpdC5odG1sKQotIFtUdXRvcmlhbCBvbiBNTklTVCBkYXRhXShQUkFDVElDRS9UdXRvcmlhbC1NTklTVC5odG1sKQoKIyMgd2l0aCBQeXRob24gY29kZSAKCi0gW0RlZXAgTmV1cmFsIG5ldHdvcms6IFByYWN0aWNlIDFdKGh0dHBzOi8vY29sYWIucmVzZWFyY2guZ29vZ2xlLmNvbS9kcml2ZS8xR0RkbVZUa19ZX1NVS3ZEclZXUGY4Q0tqLU02cjBOeUcjc2Nyb2xsVG89T0x1dWM5UXJTeEZuKQotIFtEZWVwIE5ldXJhbCBuZXR3b3JrOiBQcmFjdGljZSAyXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZHJpdmUvMWdwcjhYbDVZNTEtZFlEUTFCQTZsTmNKOHlacTNHUHZyKQotIFtEZWVwIE5ldXJhbCBuZXR3b3JrOiBQcmFjdGljZSAzXShodHRwczovL2NvbGFiLnJlc2VhcmNoLmdvb2dsZS5jb20vZHJpdmUvMTJQRVRkcEIyQjBZMm1UR3pZVVE4WDZNMEd4Um80Y1FOI3Njcm9sbFRvPWtObUItSldCYVg1OSkKLSBbRGVlcCBOZXVyYWwgbmV0d29yazogUHJhY3RpY2UgNF0oaHR0cHM6Ly9jb2xhYi5yZXNlYXJjaC5nb29nbGUuY29tL2RyaXZlLzFNc2cyR0R0NVAwa0IwTVZQU1BVY0daR0l4RGpPZ29EYj91c3A9c2hhcmluZykKCgoK